-1

Consider this scenario:

$ cat t783.c
#define EXPR ("xxx" + 1)
char* s = EXPR;

$ clang t783.c -c
t783.c:2:11: warning: adding 'int' to a string does not append to the string [-Wstring-plus-int]

$ clang t783.c -E | clang -xc - -c
t783.c:2:18: warning: adding 'int' to a string does not append to the string [-Wstring-plus-int]

Here we see that compilation of previously non-preprocessed source code (case 1) leads to the same diagnostics as compilation of previously preprocessed source code (case 2).

Question: is this always true? (Excluding, of course, the diagnostics related to the preprocessor itself.)

In other words: is diagnostics "preprocessor-insensitive"?

Reason: better understanding of compilers.

pmor
  • 5,392
  • 4
  • 17
  • 36
  • 2
    _language-lawyer_ tag: I don't think the standard says anything about how diagnostic messages should look like (aside from e.g. `#error` and so on) and for this case it also doesn't require any diagnostic message at all. – user17732522 Dec 30 '21 at 15:33
  • 1
    There is not even a rule that a compiler must issue the same diagnostics for the same source code, regardless of preprocessing. C 2018 5.1.13 1 says “A conforming implementation shall produce at least one diagnostic message … if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint,…” That’s it. If there is anything wrong with your program that requires a message, a compiler just has to point out one thing wrong with it, and that satisfies the standard. It could point out the first thing, the last thing, the worst thing, or a random thing. – Eric Postpischil Dec 30 '21 at 18:55
  • @EricPostpischil Re: "or a random thing": amazing! And this behavior is still conforming. I mean "does it need to be fixed?". I expect compilers to be deterministic. – pmor Jan 10 '22 at 20:45
  • The C standard is generally trying to define the language, not tell people how to write compilers. Standard conformance is no measure of quality; it's entirely possible for a compiler to be conforming yet completely unusable for any practical purpose. If you expect determinism, that's between you and your compiler vendor; the C standard is staying out of that argument. – Nate Eldredge Jan 11 '22 at 16:11
  • In any real compiler, it's probably not literally "random" but it may well depend subtly on factors beyond the contents of the code, that could be hard to predict. For instance, we could imagine an incremental compiler, where the diagnostic issued would depend on which part of the code you edited most recently. – Nate Eldredge Jan 11 '22 at 16:16

2 Answers2

2

No, not always.

For instance, GCC has an option -Wmultistatement-macros, enabled by -Wall, to warn when a macro expands to multiple statements that are not all guarded by the same if, while, etc. See manual. A filled-in version of the manual's example:

void foo(void);
void bar(void);

#define BLAH foo(); bar()

void qux(int cond) {
    if (cond)
        BLAH;
}

The programmer probably intends that foo() and bar() should be called only if cond is true, but actually bar() will always be called.

Running gcc -Wall -c foo.c produces the warning:

foo.c: In function ‘qux’:
foo.c:4:14: warning: macro expands to multiple statements [-Wmultistatement-macros]
    4 | #define BLAH foo(); bar()
      |              ^~~
foo.c:8:2: note: in expansion of macro ‘BLAH’
    8 |  BLAH;
      |  ^~~~
foo.c:7:5: note: some parts of macro expansion are not guarded by this ‘if’ clause
    7 |     if (cond)
      |     ^~

But gcc -E -Wall foo.c > foo.i and gcc -Wall -c foo.i separately do not produce any diagnostics at all.

Evidently there is some information shared between the preprocessing and compilation passes when you run them together, so that the compiler phase knows that foo(); bar() resulted from the expansion of a macro. But the preprocessed source file emitted by gcc -E does not itself contain this information, so when gcc is run on foo.i, it doesn't know that there was originally a macro, so it can't give the warning.


Per the tag, I see no conformance problem with this behavior. The C standard says only, roughly speaking, that an implementation must issue diagnostics in certain cases, and otherwise must translate conforming programs within implementation limits. Beyond this it is free to issue diagnostics whenever it wants, so long as they are non-fatal for a conforming program. There is no rule that the decision of whether to issue a diagnostic, or what it should say, should be solely determined by the contents of the translation unit after preprocessing. The compiler can certainly take into account the contents of the source before preprocessing, or the contents of other unrelated translation units (e.g. to detect when global declarations don't match), or the contents of other files on the system (e.g. compiler configuration files), or the current phase of the moon. No rules against it. And I can't imagine that the standard authors would have wanted to forbid a useful feature like -Wmultistatement-macros.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • `#define BLAH foo(); bar()` A bit nonsesnse example considering what OP wrote *`"(Excluding, of course, the diagnostics related to the preprocessor itself.)"`* – 0___________ Dec 30 '21 at 21:17
  • I guess it is "related" to the preprocessor, but I understood that to be about diagnostics that *only* involved the preprocessor. The multistatement macro warning involves an interaction between the preprocessor and the compiler (macro expansion and language syntax). Anyway, I suppose OP can tell us if it's what they were looking for or not. – Nate Eldredge Dec 31 '21 at 00:09
  • It can only be emitted by the preprocessor as the C compiler does not see any macros. – 0___________ Dec 31 '21 at 00:26
  • 2
    @0___________: If you insist on drawing that distinction, then please explain why `gcc -E -Wall foo.c` doesn't emit the warning. And why the string `some parts of macro expansion` appears in the `cc1` binary and not in the `cpp` binary. – Nate Eldredge Dec 31 '21 at 04:20
  • As I understand, the `-Wmultistatement-macros` (or similar) is not required per C11. Yes, per C11 "an implementation is free to produce any number of diagnostics as long as a valid program is still correctly translated". Is there are another example? – pmor Jan 10 '22 at 20:37
  • @pmor: Presumably any code that requires a diagnostic will get one, whether the preprocessor and compiler are run together or separately. But are you merely looking for one where the diagnostics in question have different text? That should be pretty easy, because compilers like gcc try to include a snippet of the offending code, prior to macro expansion if possible. If it only has the preprocessed source then it will necessarily not show the pre-expansion code. – Nate Eldredge Jan 10 '22 at 23:41
  • Re: "looking for one where the diagnostics in question have different text": looking for cases where diagnostics is different (excluding "`-Wmultistatement-macros`-like cases"). Examples: A - B, A - . And vice versa. – pmor Jan 11 '22 at 16:01
  • @pmor: And what about one where you have something like `#define foo bar`, and the messages are respectively something like `Error: illogical statement: foo` and `Error: illogical statement: bar`? – Nate Eldredge Jan 11 '22 at 16:07
  • Guess that no so interesting. Example of interesting case: `` and `warning: condition is always true`. Which is a compiler defect I assume. – pmor Jan 11 '22 at 16:18
1

Yes, it is always the truth. The compiler compiles preprocessed source so it will get the same source in both cases.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • I'm skeptical. Not all compilers separate preprocessing and compilation in the traditional way, so there could be additional information shared between the passes when you run them together. For instance, I can imagine it being useful to provide different warnings for some piece of code depending on whether or not it resulted from the expansion of a macro. – Nate Eldredge Dec 30 '21 at 16:15
  • I added an answer with a counterexample. – Nate Eldredge Dec 30 '21 at 16:45
  • @NateEldredge It is a preprocessor warning. Same as unused macro, #erroe, #warning and some few others. OP wrote *`"(Excluding, of course, the diagnostics related to the preprocessor itself.)"`* – 0___________ Dec 30 '21 at 21:21
  • Maybe it's a semantic disagreement, but I would not say it is a "preprocessor warning" because, unlike for your examples, running the preprocessor alone (`gcc -E`) does not emit the warning. – Nate Eldredge Dec 31 '21 at 00:04
  • @NateEldredge It can only be emitted by the preprocessor as the C compiler does not see any macros. – 0___________ Dec 31 '21 at 00:26
  • 1
    But conversely, traditionally the preprocessor does not know about statements or the keyword `if`. The point I am trying to make is that a modern implementation may well integrate the preprocessor and compiler more tightly, such that they share more information than just the preprocessed source. The preprocessor may inform the compiler pass, via internal data structures or some other channel, that the code in question came from expanding a macro with a particular name, even if the compiler does not see the macro itself. – Nate Eldredge Dec 31 '21 at 04:17
  • Re “the C compiler does not see any macros”: This is incorrect both theoretically (the C standard does not mention “compiler” anywhere in its normative text) and practically (preprocessing and compiling are integrated in GCC and Clang). – Eric Postpischil Jan 01 '22 at 00:26