[Rejected] RFC: Stop defining the __STDC__ (and related) macros in C++ mode

The C++ standard allows an implementation to define macros like __STDC__ and __STDC_VERSION__, etc: __STDC__ and Clang does this for some of the macros, as does GCC.

However, this is a Very Bad Idea™ for many reasons and just because we can/have doesn’t mean we should. Users, rather reasonably, expect __STDC__ to identify when they’re using a conforming C implementation because that’s how the macro is defined by the C standard in 6.10.8. You can see plenty of evidence for this

and it leads to surprising behaviors (code pulled from an example above):

(The GCC behavior probably surprises folks, it did for me; the Clang behavior is because we don’t define __has_c_attribute in C++ mode.)

However, C++ is not conforming C plus some extra stuff; it’s core language specification is completely separate from the language specification for C, and there are tons of deviations between the languages. I think it’s dangerous to give the perception that C++ is conforming C code, and I think that our defining these macros makes it significantly harder to clearly identify whether Clang is compiling in C mode or C++ mode. Users have to know about this somewhat hidden gotcha and write #ifndef __cplusplus and hope that’s sufficient in order to identify that they’re in C mode, but even that isn’t bulletproof because header files are sometimes shared between tools where the language mode may sometimes be something other than C++ but it still has a sufficiently compatible preprocessor (such as FORTRAN).

GCC also documents that this behavior is not ideal, and the conditions under which they define __STDC__ do not match how we handle it in Clang (which is unconditionally defining it outside of Microsoft compatibility mode). Standard Predefined Macros (The C Preprocessor)

I propose that we stop pretending we’re a C compiler in C++ mode, and no longer predefine the following C-based macros which we currently define today:

  • __STDC__
  • __STDC_UTF_16__
  • __STDC_UTF_32__

That said, it’s unclear to me whether this would break code in practice, and if so, how much code. It’s tricky to measure this given the myriad of creative preprocessor macros appearing in header files. I think that the only two macros which are mildly defensible to keep are the UTF macros, due to C++11 and C++14 only defining them if <cuchar> is included – however, they’re no longer mentioned in C++17 or later anywhere in the C++ standard, so I’d also be fine if we only defined those in C++11 and C++14 mode.

It’s worth noting that we can’t get rid of __STDC_HOSTED__ because the C++ standard made that a mandatory macro: __STDC_HOSTED__, and Clang has never defined __STDC_VERSION__. You could argue that users can identify C mode by the definition of __STDC_VERSION__, and I’d argue that’s still user-hostile because the definition of __STDC__ is that it’s a conforming C implementation, which C++ is not.

1 Like

I think the reason why this is the way it is, is because people generally are using __STDC__ to mean “not a completely broken pre-ANSI-C compiler”. And C++ does meet that requirement, though it’s not standard C. And with the possibility of using __STDC_VERSION__ for users available, I think this is not worth changing.

Just looking through the first few examples I found of __STDC__ outside C files in a codesearch of our codebase:

Curl expects it to be defined even in C++, but it would still work without:
https://github.com/curl/curl/blob/1ddc8aefb2e45def02dfe02973a3afd2fbdf09c3/include/curl/curl.h#L3080

zlib explicitly checks __STDC__ || __cplusplus so would be unaffected by such a change:
https://github.com/madler/zlib/blob/21767c654d31d2dccdde4330529775c6c5fd5389/zconf.h#L211

gmp.h the same.

glibc requires __STDC__ to be defined, if you claim to be GCC:
https://github.com/bminor/glibc/blob/master/misc/sys/cdefs.h#L30

…and…welp, that effectively dooms this idea for the next decade or so. I stopped looking there.

Interesting things to note from your examples:

  • As far as I can tell, BOOST_LANG_STDC appears to be used by literally nothing in the entire world.
  • Tasmota appears to consider __STDC__ to mean “strict conformance mode” or something, so it should avoid the long long extension…GCC docs mention that some other systems might use it like that – but they claimed it was defined as 0 or 1, while Tasmota is checking if it’s defined. Not sure what’s up with that, but in any case, it seems very questionable.
2 Likes

glibc requires __STDC__ to be defined, if you claim to be GCC:
…and…welp, that effectively dooms this idea for the next decade or so. I stopped looking there.

Agreed… features.h includes sys/cdefs.h. Including almost any header will pull in sys/cdefs.h, and the compile will fail:

#if defined __GNUC__ && !defined __STDC__
# error "You need a ISO C conforming compiler to use the glibc headers"
#endif

I’ve proposed a change to Glibc:

https://sourceware.org/pipermail/libc-alpha/2022-May/138738.html

1 Like

So it does. DRAT! Thank you for noticing that and reporting back.

I was chatting with @jwakely about it this morning and he’s proposed a patch to resolve this in glibc. So maybe in a decade we can revisit not being actively user-hostile to C programmers again.

I’ve changed the title of the RFC to be clear it was rejected for the time being.

Here’s a wild idea: You could always add a -std=clang++20 mode that defines neither __GNUC__ nor __STDC__ but that’s probably a bigger can of worms to open.

Edit: that would definitely break things, but maybe it should happen one day. Inappropriate reliance on __GNUC__ is an anti-pattern just like reliance on __STDC__.

1 Like

What’s next? Stop telling the world that we’re GCC 4.2 with our macros? My goodness, you’d almost think that lying to users was a bad idea that eventually comes back to haunt you. :smiley:

I agree that would be a huge can of worms that’s probably worth opening at some point.

3 Likes

Can we open it now (maybe in another thread?). I would really like to see this get fixed.

Can we open it now (maybe in another thread?). I would really like to see this get fixed.

Which can of worms do you want to open – adding a -std=clang++20 mode so that we can stop pretending to be GNU, or stop telling the world we’re GCC 4.2 (perhaps tell them we’re a newer GCC), or both? I’d say it’s probably worth opening a new thread (if we want to do any of the above).

What’s next? Stop telling the world that we’re GCC 4.2 with our macros?

If anyone wants to test drive that, you can do it with -fgnuc-version=0: Compiler Explorer

I added this flag years ago for this purpose and never publicized it or followed up on it.

1 Like

I’m talking about clang not pretending to be gcc at all. Maybe -std=clang++20 would be a nice transition step, but I think at this point it’s doing more harm than good to masquerade as gcc.

1 Like

This is very clearly not the case. Approximately no code is currently using defined(__GNUC__) || defined(__clang__), when using features that are common to both GCC and Clang, which work in both compilers. I’m not even going to go do a codesearch to find examples, they are so plentiful.

There is just about zero chance clang could ever stop setting GNUC without breaking the world.

I’m guessing we probably spend our time looking at very different code bases, because I see examples of defined(GNUC) || defined(clang) just searching the source code on my system. I can do some mass rebuilds of Fedora packages using the option recommended by @rnk to try to get an idea of what the impact of dropping GNUC would be.

1 Like

So, I did some initial testing and you were right. The glibc headers depend on GNUC being defined, so pretty much nothing compiled correctly. I don’t want to say it’s impossible to ever not set this, but there is a lot of work that needs to be done.