Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding a double macro subsitution in the C pre-processor

Tags:

c

linux

gcc

macros

Here's a simple little C program that had me confused for a while:

#include <stdio.h>
#define STR1(x) #x
#define STR(x) STR1(x)
int main(void) {
    printf("%s\n", STR(MYDEF));
}

This just prints the value of the MYDEF #define as a string, using the standard stringizing double-define technique.

Compile (on Linux) with gcc -DMYDEF=abc prog.c run the result and, not surprisingly, it prints out 'abc'.

But change the value gcc -DMYDEF=linux prog.c and the result printed is not 'linux' but '1'.

So that confused me for a bit, but of course it happens because gcc (on Linux) has, I discovered, a built-in #define for the name 'linux' with a value '1', and the STR(x) macro ends up expanding MYDEF to 'linux' then linux to '1'.

In my real program (which was rather more complex than the little test above) I got round this by doing things in a different (probably better) way, but it left me curious ... is there a simple little macro technique that would avoid this double-substitution and make the program print out 'linux'? I know I could add a -U or #undef of linux, but that feels a bit clumsy.

I had thought all the built-in #defines start with underscores (usually double underscores), but I guess not.

like image 392
tjb300 Avatar asked Oct 31 '22 12:10

tjb300


2 Answers

There is no way to expand a macro only once, there's always a rescan performing further replacement (never recursive, of course). There are circumstances where macros aren't expanded at all (as with the # operator), which is why you need the extra replacement level with two #define like in your example.

In ISO C, identifiers without a leading underscore are free for you to use (not all of them, to be precise). The GNU C dialects define some other macros by default (like linux) for backwards compatibility, though they plan to remove such macros in the future.

To get a list of such macros on your machine, you can do:

$ echo | gcc -std=gnu99 -E -dM - | grep -v '# *define  *_'
#define unix 1
#define linux 1
#define i386 1

With the options for ISO C (-ansi/-std=c89, -std=c99, -std=c11/-std=c1x for older Gcc), these macros are not defined:

$ cat test.c     
#define STR1(x) #x
#define STR(x) STR1(x)
STR(MYDEF);
STR1(MYDEF);
$ gcc -std=gnu99 -DMYDEF=linux -E test.c
# 1 "test.c"
# 1 "<command-line>"
# 1 "test.c"


"1";
"MYDEF";
$ gcc -std=c99 -DMYDEF=linux -E test.c
# 1 "test.c"
# 1 "<command-line>"
# 1 "test.c"


"linux";
"MYDEF";

In ISO C mode, these macros properly are in the reserved namespace:

$ echo | gcc -std=c99 -E -dM - | grep linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1
like image 182
mafso Avatar answered Nov 11 '22 17:11

mafso


I see in the gcc manual that you can use the -ansi option to turn off predefined macros like "linux"

gcc -ansi -DMYDEF=linux prog.c
like image 24
Yetti99 Avatar answered Nov 11 '22 17:11

Yetti99