One of my pet-peeves with C projects is that it's so often more or less "works on my machine" when written by Linux users (as a Windows and FreeBSD user it often hits you on both those platforms).
The article highlights a typical piece:
#if !(defined __GNUC__ || defined __clang__ || defined __TINYC__)
# define __attribute__(xyz) /* Ignore */
#endif
There is no reason that !defined check to not include a check for __attribute__ already being defined (a custom compiler author could then force an define for __attribute__ that translates to an internal __mycompiler__attribute__ replacement by default).
But outside of that, just trying to compile on FreeBSD you often run into systemd dependencies or other non-posix behaviors (Not to mention on Windows but I'm not here to bring on flamewars so I'll leave that part).
In our compiler (in a code analysis tool), we have
#pragma immutable_macro __attribute__
After this pragma, any attempt to #define/#undef the macro "__attribute__" will be silently ignored. This lets us (or our customers) bypass such stupidity in library headers. It's also often useful to replace broken macros with working versions.
Yes, when I implemented ImportC (a C compiler built in to the D compiler), I had to spend a lot of time finding ways to work with all the nutburger nonsense in the various .h files.
For those who are making indie C compilers that don't pretend to be __GNUC__ but want to compile real world projects, slimcc's test script[1] and platform header hacks[2] might save you some time.
[1] https://github.com/fuhsnn/slimcc/blob/main/scripts/linux_thi...
- Game projects default to using SIMD so for example SDL and STB you always need to pass -DSDL_DISABLE_IMMINTRIN_H and -DSTB_NO_SIMD
- math.h's NAN usually fall back to (0.0f / 0.0f), which will print "-nan" with printf, some projects test suite fail because of it (they expected "nan").
- NetBSD's sys/cdefs.h straight up #error's if you don't pretend to be GCC or PCC.
- Some projects can't compile without __attribute__((always_inline)) because they use it on non-static functions.
- Many projects probe -fvisibility in the build system and pass -fvisibility=hidden to compile, but in the headers they gate __attribute__((visibility(default))) behind __GNUC__ checks, so you'll get missing symbols.
- Some projects use if(0) { undefined_function() } to fake static_assert(), there is even a bug report from QEMU to Clang because it failed to optimize in -O0 a certain `if` written this way.
- Even if you define __STDC_NO_VLA__, projects might fall back to alloca() code path that's untested and broken (python and jemalloc both had this problem, already reported)
- Valkey has broken __builtin_ctzll fallback nobody noticed (reported).
- Zig's C bootstrap path expects the compiler to have GCC/Clang-tier optimization and stack overflows if you don't (reported).
- I contributed stdatomic.h code path for Ruby just to compile it with slimcc, pretty sure it's still the only user of the code path.
- I implemented __has_extension in the hope that projects can use it to query gnu_asm; but SQLite broke because they use __has_extension(c_atomic) to query GNU atomics builtin, but c_atomic actually is meant for C11 _Atomic (IMO they should use __has_builtin)
Although I don't use slimcc, I follow its development because I learn so much about C - even though I've been using C and C++ for over 30 years. https://github.com/fuhsnn/slimcc/blob/main/scripts/linux_thi... is an incredible piece of work. I can't imagine how many hours it took to assemble this collection. If slimcc can pass that project list torture test, then surely it's bulletproof.
I just implemented a fast small compiler rcc to compete against tcc, and these glibc header quirks were simply fixed in the same way clang does it. By defining all gcc predefines and implementing all gcc extensions. Needed a day. And my headers are clean compared to glibc. The others in this league are slimcc, kefir and cproc. No other compilers can parse glibc headers. tcc has this special exception.
> Anyone who's written C knows that full ISO C standard-adhering code is an impractical rarity.
I've written quite a bit of C code and do not know that to be a rarity. Especially when it comes to libraries rather than applications, and FOSS as opposed to proprietary code.
> Most real world C code out there relies on non-standard behaviors and language extensions to varying extents
Maybe it depends on which domain you're working in. At companies whose target platform is not a PC, relying on idiosycratic behavior, or extensions, is difficult: The compiler for the target device may simply not support the bells and whistles of GCC or whatever, so you stick to C99, (or even C89, ugh) to be on the safe side. And even then there will be things which are standard, but... well, I would be wary of relying on them being supported robustly enough, e.g. variable-length arrays.
And of course, once your code does not target just one single machine then you're forced to have to worry about portability and standard compliance etc.
I think the Common Lisp ecosystem sets a good example of how a dozen of implementations move ahead together. Implementations experiment with extensions, the really useful ones get implemented multiple times, and some portability library emerges as de-facto standard if it's good enough. You can watch the result in https://portability.cl/, the language is evolving like never before even if the standard committee has dissolved nearly 40 years ago!
I'm in the middle of targeting Dart C FFI for Jank lang. The Zig toolchain has been helpful for simplifying the complexity, similar to how Bun simplified JS devwork. This article is relevant as some libraries have to be considered in building the jank package with glib libraries on a Nix flake.
> For example, clang defines __GNUC__=4 (and __GNUC_MINOR__=2, __GNUC_PATCHLEVEL__=1) to claim compatibility with GCC 4.2.1.
Is this because Apple at some point stopped updating GCC because of the GPL license change? I think GCC 4.2.x was the last GCC licensed under GPLv2. Could be for compatibility reasons for Mac software.
20 comments
[ 4.4 ms ] story [ 38.6 ms ] threadThe article highlights a typical piece:
There is no reason that !defined check to not include a check for __attribute__ already being defined (a custom compiler author could then force an define for __attribute__ that translates to an internal __mycompiler__attribute__ replacement by default).But outside of that, just trying to compile on FreeBSD you often run into systemd dependencies or other non-posix behaviors (Not to mention on Windows but I'm not here to bring on flamewars so I'll leave that part).
https://github.com/dlang/dmd/blob/master/druntime/src/import...
https://github.com/dlang/dmd/blob/master/druntime/src/__impo...
[2] https://github.com/fuhsnn/slimcc/blob/main/slimcc_headers/pl...
Some more fun stories:
- Game projects default to using SIMD so for example SDL and STB you always need to pass -DSDL_DISABLE_IMMINTRIN_H and -DSTB_NO_SIMD
- math.h's NAN usually fall back to (0.0f / 0.0f), which will print "-nan" with printf, some projects test suite fail because of it (they expected "nan").
- NetBSD's sys/cdefs.h straight up #error's if you don't pretend to be GCC or PCC.
- Some projects can't compile without __attribute__((always_inline)) because they use it on non-static functions.
- Many projects probe -fvisibility in the build system and pass -fvisibility=hidden to compile, but in the headers they gate __attribute__((visibility(default))) behind __GNUC__ checks, so you'll get missing symbols.
- Some projects use if(0) { undefined_function() } to fake static_assert(), there is even a bug report from QEMU to Clang because it failed to optimize in -O0 a certain `if` written this way.
- Even if you define __STDC_NO_VLA__, projects might fall back to alloca() code path that's untested and broken (python and jemalloc both had this problem, already reported)
- Valkey has broken __builtin_ctzll fallback nobody noticed (reported).
- Zig's C bootstrap path expects the compiler to have GCC/Clang-tier optimization and stack overflows if you don't (reported).
- I contributed stdatomic.h code path for Ruby just to compile it with slimcc, pretty sure it's still the only user of the code path.
- I implemented __has_extension in the hope that projects can use it to query gnu_asm; but SQLite broke because they use __has_extension(c_atomic) to query GNU atomics builtin, but c_atomic actually is meant for C11 _Atomic (IMO they should use __has_builtin)
Considering such checks are fairly conventional in downstream C++ libraries based on compilers (for example checking OS platform or compiler, e.g. [Boost.Config](https://www.boost.org/doc/libs/latest/libs/config/). Modern C++ even went ahead and standardized this somewhat https://en.cppreference.com/cpp/utility/feature_test )
I've written quite a bit of C code and do not know that to be a rarity. Especially when it comes to libraries rather than applications, and FOSS as opposed to proprietary code.
> Most real world C code out there relies on non-standard behaviors and language extensions to varying extents
Maybe it depends on which domain you're working in. At companies whose target platform is not a PC, relying on idiosycratic behavior, or extensions, is difficult: The compiler for the target device may simply not support the bells and whistles of GCC or whatever, so you stick to C99, (or even C89, ugh) to be on the safe side. And even then there will be things which are standard, but... well, I would be wary of relying on them being supported robustly enough, e.g. variable-length arrays.
And of course, once your code does not target just one single machine then you're forced to have to worry about portability and standard compliance etc.
Tell me about it. :-)
We need more betterC compilers, we have one (five if you count C3, ZenC, SafeC and Zig but they are not betterC)
And we need more assemblers.
Is this because Apple at some point stopped updating GCC because of the GPL license change? I think GCC 4.2.x was the last GCC licensed under GPLv2. Could be for compatibility reasons for Mac software.