ChatGPT解决这个技术问题 Extra ChatGPT

How does the C preprocessor handle circular dependencies?

I want to know how the C preprocessor handles circular dependencies (of #defines). This is my program:

#define ONE TWO 
#define TWO THREE
#define THREE ONE

int main()
{
    int ONE, TWO, THREE;
    ONE = 1;
    TWO = 2;
    THREE = 3;
    printf ("ONE, TWO, THREE = %d,  %d, %d \n",ONE,  TWO, THREE);
}

Here is the preprocessor output. I'm unable to figure out why the output is as such. I would like to know the various steps a preprocessor takes in this case to give the following output.

# 1 "check_macro.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "check_macro.c"

int main()
{
 int ONE, TWO, THREE;
 ONE = 1;
 TWO = 2;
 THREE = 3;
 printf ("ONE, TWO, THREE = %d,  %d, %d \n",ONE, TWO, THREE);
}

I'm running this program on linux 3.2.0-49-generic-pae and compiling in gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5).


r
rici

While a preprocessor macro is being expanded, that macro's name is not expanded. So all three of your symbols are defined as themselves:

ONE -> TWO -> THREE -> ONE (not expanded because expansion of ONE is in progress)
TWO -> THREE -> ONE -> TWO (        "                         TWO      "        )
THREE -> ONE -> TWO -> THREE (      "                         THREE    "        )

This behaviour is set by §6.10.3.4 of the C standard (section number from the C11 draft, although as far as I know, the wording and numbering of the section is unchanged since C89). When a macro name is encountered, it is replaced with its definition (and # and ## preprocessor operators are dealt with, as well as parameters to function-like macros). Then the result is rescanned for more macros (in the context of the rest of the file):

2/ If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file’s preprocessing tokens), it is not replaced. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced…

The clause goes on to say that any token which is not replaced because of a recursive call is effectively "frozen": it will never be replaced:

… These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

The situation which the last sentence refers rarely comes up in practice, but here is the simplest case I could think of:

#define two one,two
#define a(x) b(x)
#define b(x,y) x,y
a(two)

The result is one, two. two is expanded to one,two during the replacement of a, and the expanded two is marked as completely expanded. Subsequently, b(one,two) is expanded. This is no longer in the context of the replacement of two, but the two which is the second argument of b has been frozen, so it is not expanded again.


+1, excellent answer. Here's an example that I feel nicely demonstrates this behavior (but, alas, is a bit too long for a comment).
@IlmariKaronen: I added an example for the last sentence of paragraph 2, which is otherwise a bit tricky to understand. But rereading your comment/answer, I don't think that was what you were aiming at, so there's no need to say that your example is roughly the same as the OP, although the result is possibly a little more visual.
E
Eric Lippert

Your question is answered by publication ISO/IEC 9899:TC2 section 6.10.3.4 "Rescanning and further replacement", paragraph 2, which I quote here for your convenience; in the future, please consider reading the specificaftion when you have a question about the specification.

If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file’s preprocessing tokens), it is not replaced. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.


To be fair, finding and understanding the answer in the C standard is not a trivial task. With your "go read the standard" logic, we could answer every single question related to C with RTFM.
@Lundin: The specification begins with a table of contents which clearly identifies which section of the specification is about macro expansion; it took me all of 30 seconds to find the correct paragraph, and I am no expert on the C specification. And yes, with my excellent suggestion that people actually read the standard when they have a question about a standardized language, most of the bad questions in this tag would go away. That's a good thing.
Except this isn't a bad question. The OP has done some research, included an example that compiles and the pre-processor output, specified the compiler and system etc. And there seems to be no obvious duplicates of the question. Again, reading the C standard is no trivial task. For example, you didn't manage to do so. You are for some reason citing a draft N1124 to ISO 9899:1999 TC2, which has since then been replaced with C99+TC2, C99+TC3 draft N1256, C99+TC3, C11, C11+TC1. Though I'm sure you know all the changes of macro rescanning through these revisions...
@Lundin: As far as bad questions go, I see 100x worse questions here every day, so yes, it's pretty good. I chose that version of the standard because it's easily found -- it's linked from Wikipedia -- and its free, and most compiler comply to it. As I said, I am not an expert at all on the history or contents of the C specification; my point is that I managed to find an answer to the question with one web search and one glance at the table of contents; this is not out of the realm of possibility for the average programmer. I encourage spec reading to be in the toolbox of all programmers.
@Alice: Though cHao could perhaps have expressed themselves more elegantly, the point is well taken. I am basically unconcerned with the correctness proofs of hundred line methods in library code that has a clear specification; I'm concerned about the correctness of entire operating systems, entire databases, and so on, operating in a world with weak memory models, memory-unsafe languages and so on. The techniques that you use to prove the correctness of the STL collections do not scale to the entire Windows OS.
R
R Sahu

https://gcc.gnu.org/onlinedocs/cpp/Self-Referential-Macros.html#Self-Referential-Macros answers the question about self referential macros.

The crux of the answer is that when the pre-processor finds self referential macros, it doesn't expand them at all.

I suspect, the same logic is used to prevent expansion of circularly defined macros. Otherwise, the preprocessor will be in an infinite expansion.


M
M.M

In your example you do the macro processing before defining variables of the same name, so regardless of what the result of the macro processing is, you always print 1, 2, 3!

Here is an example where the variables are defined first:

#include <stdio.h>
int main()
{
    int A = 1, B = 2, C = 3;
#define A B
#define B C
//#define C A
    printf("%d\n", A);
    printf("%d\n", B);
    printf("%d\n", C);
}

This prints 3 3 3. Somewhat insidiously, un-commenting #define C A changes the behaviour of the line printf("%d\n", B);


2
2 revs

Here's a nice demonstration of the behavior described in rici's and Eric Lippert's answers, i.e. that a macro name is not re-expanded if it is encountered again while already expanding the same macro.

Content of test.c:

#define ONE 1, TWO
#define TWO 2, THREE
#define THREE 3, ONE

int foo[] = {
  ONE,
  TWO,
  THREE
};

Output of gcc -E test.c (excluding initial # 1 ... lines):

int foo[] = {
  1, 2, 3, ONE,
  2, 3, 1, TWO,
  3, 1, 2, THREE
};

(I would post this as a comment, but including substantial code blocks in comments is kind of awkward, so I'm making this a Community Wiki answer instead. If you feel it would be better included as part of an existing answer, feel free to copy it and ask me to delete this CW version.)


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now