-1

Does this code lead to well-defined behavior? If no, then which rules / statements of C standard are violated? How exactly they are violated?

Code #1:

#define B(a1, ...)  a1
int main ( void )
{
    return B(0);
}

Code #2:

#define M1(...)     M2(0
#define M2(a)       a
int main ( void )
{
    return M1(0));
}
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
pmor
  • 5,392
  • 4
  • 17
  • 36
  • I'm not sure if `M2` would be recognized by the preprocessor, since the macro is fragmented across rewrite sequences (the `)` that completes the macro is outside the rewrite section, so the macro within the section is incomplete)... you could probably find a workaround using another layer of macro so no macro is fragmented, but either way, I'm not sure why you would want this. – Myst Dec 06 '20 at 23:06
  • These questions should be posed separately. – Eric Postpischil Dec 06 '20 at 23:55
  • It is a blatant and unnecessary violation of the [right door rules](http://3.bp.blogspot.com/-ilMjE1Gh3Yg/VpUAmd-6TWI/AAAAAAAAAbg/-FJ08zxN42s/s1600/WFTPM.png). – Hans Passant Dec 07 '20 at 00:07
  • There’s no undefined behavior in the preprocessor AFAIK, other than by arithmetic overflow in `#if` expressions. So what you get here may be unexpected but there’s nothing undefined about it if it doesn’t trigger an error. – Kuba hasn't forgotten Monica Dec 07 '20 at 00:32
  • `Code #1:` `Code #1:` Why not `Code #2:`...? You asked two questions in your post - if you can pass less arguments and if you can add the closing bracket `)` after expansion. It's preferred to ask one question per question. – KamilCuk Dec 07 '20 at 10:09
  • 2
    @UnslanderMonica, most "shall" in the standard for the preprocessor does indeed appear in constraint sections, so violating them requires a diagnostic. But not all are of that kind, and the first example given here has indeed no defined behavior. – Jens Gustedt Dec 07 '20 at 10:10
  • 1
    @JensGustedt Good catch. Those are IMHO technical deficiencies in the standard - there's no need for this to be undefined... :( – Kuba hasn't forgotten Monica Dec 07 '20 at 16:55
  • 1
    @UnslanderMonica, I don't think so. UB has different reason in the C Standard. One of them is to leave slack to implementations to provide their own definitions, and this is probably the case for such UB in the preprocessor. – Jens Gustedt Dec 07 '20 at 17:09
  • 1
    @JensGustedt Providing "slack" to implementations in usually makes the feature useless, because you can't depend on any particular behavior in portable code. But I learned something new - I didn't realize that such basic preprocessor behavior was undefined. It could have been at least implementation-defined, and that usually means that the implementers at least bother to document what behavior they put in. With undefined behavior in preprocessor, most implementations don't even bother documenting it, so you can not depend on it not changing in next release, never mind on another compiler... – Kuba hasn't forgotten Monica Dec 07 '20 at 17:27
  • @UnslanderMonica @Jens Gustedt For `Code #1` another compiler produces `missing argument for "..." parameter` error (even not a warning!). This is the illustrative example of `leaving slack to implementations to provide their own definitions`. – pmor Dec 07 '20 at 22:38
  • 1
    @UnslanderMonica: Re “useless, because you can't depend on any particular behavior in portable code”: So? Not everybody’s goal is to create code that is universally portable. A foundational characteristic of C was that it was designed to be flexible so that it could provide a programming language on a great many platforms, and it succeeded tremendously in that regard. No longer did everybody have to learn assembly on a new processor or the manufacturer have to tediously construct some new compiler; there was a common base for programming that could be ported… – Eric Postpischil Dec 07 '20 at 23:01
  • … That did not mean every C program would run the same way on every processor. But it did mean many programs could be ported with much less work than was required before and programmers could work on new machines with much less learning than before. It provided a huge value and still does. Saying this is useless is nonsense and grossly ignorant of the industry. Even today, people still use C to target specific systems, deliberately using extensions and characteristics that are not portable because they provide considerable value for their purposes. – Eric Postpischil Dec 07 '20 at 23:03
  • 1
    Not sure why you added code #3. If you wish to ask follow up questions, please post them as a new question and link to this one if needed. "Moving the goal posts" of the question is discouraged, better to ask separate questions. – Lundin Dec 08 '20 at 08:32
  • @EricPostpischil: Even more significantly, C was designed to avoid the need to have programmers who moved to a new platform learn not only the details of the platform, but also learn a new language for the purpose of programming it. There's only one "portable" program for freestanding implementations: `int main(void) { while(1); }`. The language would be completely useless for freestanding implementations if implementations didn't extend the semantics beyond what the Standard requires. – supercat Dec 08 '20 at 20:41

2 Answers2

3

This answer addresses Code #2.

The behavior for the code is defined by the C standard and results in 0. In M1(0)), M1(0) is a normal macro invocation. It is replaced by M2(0. Per C 2018 6.10.3.4 1, “The resulting preprocessing token sequence is then rescanned, along with all subsequent preprocessing tokens of the source file, for more macro names to replace.” So M2(0 is the resulting sequence and ) starts the subsequent tokens, so M2(0) is scanned for more macro names to replace. M2(0) is found and is replaced with 0.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

For Code #1 there is this clause (6.10.3 p4)

Otherwise, there shall be more arguments in the invocation than there are parameters in the macro definition (excluding the ...). There shall exist a ) preprocessing token that terminates the invocation.

The "shall" here indidcates the behavior of the invocation is indeed undefined. Undefined behavior here just means that the standard leaves slack to implementations to do what ever they want in that situation. E.g gcc and clang are perfectly happy with this.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177