Getting started: using existing macros

Getting started: using existing macros#

Before defining our own macros, let’s see how they are used.

Constants#

The most basic usage of macros is constants:

 1#include <stdio.h>  // dprintf
 2#include <stdlib.h> // atoi, NULL, EXIT_*
 3#include <string.h> // strchr
 4#include <unistd.h> // *_FILENO
 5
 6/* Output the environment as a markdown table */
 7int main(int arg_count, char** arg_values, char** environment)
 8{
 9    char*    equal;
10    unsigned max = arg_count > 1 ? atoi(arg_values[1]) : 7;
11
12    dprintf(STDOUT_FILENO, "__Environment__:\n| Name | Value |\n|:-|:-|\n");
13    do
14    {
15        if ((equal = strchr(*environment, '=')) == NULL)
16        {
17            dprintf(STDERR_FILENO, "Expected an assignation but got \"%s\"\n", *environment);
18            return EXIT_FAILURE;
19        }
20        *equal = '\0';
21        dprintf(STDOUT_FILENO, "|`%s`|`%1.30s`|\n", *environment, equal + 1);
22    } while (--max > 0 && *++environment);
23    return EXIT_SUCCESS;
24}
 1#include <stdio.h>  // dprintf
 2#include <stdlib.h> // atoi, NULL, EXIT_*
 3#include <string.h> // strchr
 4#include <unistd.h> // *_FILENO
 5
 6/* Output the environment as a markdown table */
 7int main(int arg_count, char** arg_values, char** environment)
 8{
 9    char*    equal;
10    unsigned max = arg_count > 1 ? atoi(arg_values[1]) : 7;
11
12    dprintf(STDOUT_FILENO, "__Environment__:\n| Name | Value |\n|:-|:-|\n");
13    do
14    {
15        if ((equal = strchr(*environment, '=')) == NULL)
16        {
17            dprintf(STDERR_FILENO, "Expected an assignation but got \"%s\"\n", *environment);
18            return EXIT_FAILURE;
19        }
20        *equal = '\0';
21        dprintf(STDOUT_FILENO, "|`%s`|`%1.30s`|\n", *environment, equal + 1);
22    } while (--max > 0 && *++environment);
23    return EXIT_SUCCESS;
24}
/* Output the environment as a markdown table */
int main(int arg_count, char** arg_values, char** environment)
{
    char*    equal;
    unsigned max = arg_count > 1 ? atoi(arg_values[1]) : 7;

    dprintf(1 /* Standard output.  */, "__Environment__:\n| Name | Value |\n|:-|:-|\n");
    do
    {
        if ((equal = strchr(*environment, '=')) == ((void*)0))
        {
            dprintf(2 /* Standard error output.  */,
                    "Expected an assignation but got \"%s\"\n",
                    *environment);
            return 1 /* Failing exit status.  */;
        }
        *equal = '\0';
        dprintf(1 /* Standard output.  */, "|`%s`|`%1.30s`|\n", *environment, equal + 1);
    } while (--max > 0 && *++environment);
    return 0 /* Successful exit status.  */;
}

Environment:

Name

Value

GITHUB_STATE

/home/runner/work/_temp/_runne

STATS_TRP

true

DOTNET_NOLOGO

1

DEPLOYMENT_BASEPATH

/opt/runner

USER

runner

CI

true

GITHUB_ENV

/home/runner/work/_temp/_runne

As you can see in the preprocessed tab, those constants are evaluated before compilation, and replaced by plain literals:

Macros used in the example

Macro

Expansion

STDOUT_FILENO

1

STDERR_FILENO

2

EXIT_FAILURE

1

EXIT_SUCCESS

0

NULL

((void*)0)

Why are macros used in this situation ?

Why wasn’t a constant used instead ? For instance, extern FILE *const stderr; is a constant, why wasn’t STDERR_FILENO defined as extern int const stderr_fileno; ?

Differences include:

  • A constant has to be defined in a .c source file

  • that will be compiled into a library

  • So to use it one must link to that library

  • It can’t be inlined as the value isn’t known at compile-time

  • At run-time, the constant’s value needs to be fetched (extra instruction(s))

  • In the case of dynamic libraries (.so/.dll), the value can be changed between runs.

  • Also, the value of a macro can depend on macros defined by the user when including the header, while a constant has the same value for all users.

So, using macros saves a few instructions and bytes, which might seem pointless nowadays. It should also be noted that many libraries are “header-only” and so don’t have the option of defining an extern constant.

There is an ill-advised third option: define the constant as static and define it in the header. It will create a copy with its own address in each compilation unit that includes it.

Debugging constants#

Speaking of existing macro constants we can use, there exist certain macro constants, whose value is not always the same… Such behavior can only be achieved from within the compiler, and we will not be able to create our own.

The C standard requires compilers to define certain special macros, without the need to include any header, including:

  • __FILE__ expands to the name of the file currently compiled, as a C string literal

  • __LINE__ expands to the line number currently compiled, as an integer literal

Source: GNU

Additionnally, the GNU C extension include:

  • __FILE_NAME__ similar to __FILE__ but only includes the file name, excluding the path

Source: GNU Clang

While we’re at it, let’s also mention:

  • __FUNCTION__ (also __func__) is a magic constant character array that contains the name of the current function

  • __PRETTY_FUNCTION__ is similar but includes the whole signature, including return type and parameters

Warning

They are not macros, but runtime constants. As the GNU documentation puts it:

These identifiers are variables, not preprocessor macros, and may not be used to initialize char arrays or be concatenated with string literals.

Source: GNU

 1#include <stdbool.h> // bool
 2#include <stdio.h>   // printf
 3
 4void foo(void)
 5{
 6    printf("%s@%s:%i\n", __FILE_NAME__, __FUNCTION__, __LINE__);
 7}
 8
 9bool bar(int arg)
10{
11    printf("In file %s in function %s at line %i: arg=%i\n", __FILE__, __PRETTY_FUNCTION__, __LINE__, arg);
12    return true;
13}
14
15int main()
16{
17    foo();
18    bar(42);
19}
 1#include <stdbool.h> // bool
 2#include <stdio.h>   // printf
 3
 4void foo(void)
 5{
 6    printf("%s@%s:%i\n", __FILE_NAME__, __FUNCTION__, __LINE__);
 7}
 8
 9bool bar(int arg)
10{
11    printf("In file %s in function %s at line %i: arg=%i\n", __FILE__, __PRETTY_FUNCTION__, __LINE__, arg);
12    return true;
13}
14
15int main()
16{
17    foo();
18    bar(42);
19}
void foo(void)
{
    printf("%s@%s:%i\n", "02_debug.c", __FUNCTION__, 6);
}

_Bool bar(int arg)
{
    printf("In file %s in function %s at line %i: arg=%i\n",
           "samples/02_debug.c",
           __PRETTY_FUNCTION__,
           11,
           arg);
    return 1;
}

int main()
{
    foo();
    bar(42);
}
02_debug.c@foo:6
In file samples/02_debug.c in function _Bool bar(int) at line 11: arg=42

Function-like#

Macros can also take parameters:

 1#include <termios.h> // struct termios
 2
 3#include <stddef.h> // offsetof
 4#include <stdio.h>  // printf
 5
 6#if __has_include("linux/stddef.h")
 7#   include <linux/stddef.h> // sizeof_field
 8#endif
 9#ifndef sizeof_field
10#   define sizeof_field(Type, Member) sizeof(((Type){}).Member)
11#endif
12
13/** Learn about the memory layout of the termios struct */
14int main()
15{
16    printf("__Termios structure__:\n| Name | Position | Size |\n|:-|-:|-:|\n");
17    printf("|`c_iflag`|%zu|%zu|\n",
18           offsetof(struct termios, c_iflag),
19           sizeof_field(struct termios, c_iflag));
20    printf("|`c_cflag`|%zu|%zu|\n",
21           offsetof(struct termios, c_cflag),
22           sizeof_field(struct termios, c_cflag));
23    printf("|`c_lflag`|%zu|%zu|\n",
24           offsetof(struct termios, c_lflag),
25           sizeof_field(struct termios, c_lflag));
26    printf("|`c_cc`|%zu|%zu|\n",
27           offsetof(struct termios, c_cc),
28           sizeof_field(struct termios, c_cc));
29}
 1#include <termios.h> // struct termios
 2
 3#include <stddef.h> // offsetof
 4#include <stdio.h>  // printf
 5
 6#if __has_include("linux/stddef.h")
 7#   include <linux/stddef.h> // sizeof_field
 8#endif
 9#ifndef sizeof_field
10#   define sizeof_field(Type, Member) sizeof(((Type){}).Member)
11#endif
12
13/** Learn about the memory layout of the termios struct */
14int main()
15{
16    printf("__Termios structure__:\n| Name | Position | Size |\n|:-|-:|-:|\n");
17    printf("|`c_iflag`|%zu|%zu|\n",
18           offsetof(struct termios, c_iflag),
19           sizeof_field(struct termios, c_iflag));
20    printf("|`c_cflag`|%zu|%zu|\n",
21           offsetof(struct termios, c_cflag),
22           sizeof_field(struct termios, c_cflag));
23    printf("|`c_lflag`|%zu|%zu|\n",
24           offsetof(struct termios, c_lflag),
25           sizeof_field(struct termios, c_lflag));
26    printf("|`c_cc`|%zu|%zu|\n",
27           offsetof(struct termios, c_cc),
28           sizeof_field(struct termios, c_cc));
29}
/** Learn about the memory layout of the termios struct */
int main()
{
    printf("__Termios structure__:\n| Name | Position | Size |\n|:-|-:|-:|\n");
    printf("|`c_iflag`|%zu|%zu|\n",
           __builtin_offsetof(struct termios, c_iflag),
           sizeof(((struct termios){}).c_iflag));
    printf("|`c_cflag`|%zu|%zu|\n",
           __builtin_offsetof(struct termios, c_cflag),
           sizeof(((struct termios){}).c_cflag));
    printf("|`c_lflag`|%zu|%zu|\n",
           __builtin_offsetof(struct termios, c_lflag),
           sizeof(((struct termios){}).c_lflag));
    printf("|`c_cc`|%zu|%zu|\n",
           __builtin_offsetof(struct termios, c_cc),
           sizeof(((struct termios){}).c_cc));
}

Termios structure:

Name

Position

Size

c_iflag

0

4

c_cflag

8

4

c_lflag

12

4

c_cc

17

32