Whenever possible, I try to compile using -std=c11 or -std=c++11 or whatever, rather than the gnu11 or gnu++11 variants. That makes it not provide the GNU extensions directly, and instead requires double-underscore (e.g. typeof(foo) and asm("...") won't work; you instead need to do __typeof(foo) and __asm("...") if you really need it, which should make it more apparent when you're relying on non-standard features). Unfortunately CMake sets the gnu/gnu++ variants when you request a specific standards version, and people don't generally consider the downsides to using gnu/gnu++ modes with code that's supposed to be portable, and instead think "more is better".Graf Zahl wrote:And as things stand, GCC is by far the worst in this regard, mainly because it advertises itself as a standard compliant compiler and thus making many programmers use its non-standard features.
Undefined behavior is what allows the kind of optimizations C and C++ thrive on. Aliasing rules (undefined behavior if you access the same memory through pointers to different types) and memory concurrency (undefined behavior if you non-atomically read memory in one thread that could be written in another), for instance, is what allows the compiler to avoid treating all memory accesses as volatile with hard memory barriers. Signed overflow (undefined result if incrementing or multiplying a signed integer beyond its max value) is what allows all those for(int i = 0;condition;i++) ... loops to remain efficient on x86-64, since internally 64-bit CPUs use 64-bit offset indexing while an int is typically only 32 bits. The compiler can substitute the 32-bit variable with a native 64-bit register offset, since it can assume i will never increment beyond INT_MAX. Unsigned integer overflow, in contrast, is defined (it must wrap), and that means the compiler needs to make CPUs with 64-bit addressing do more work to ensure for(unsigned int i = 0;condition;i++) will maintain proper indexing in the event of an overflow.Of course all that doesn't even come close to touching the most annoying subject of C++ programming, and that is the bootload of undefined behavior and a committee that seems to be so stuck in the past - forgetting that any bit of undefined or implementation defined behavior is a source for major trouble.
Undefined and implementation defined behavior is also what allows C++ to be portable and somewhat future-proof. Not all microprocessors implement types using the same design. 2's compliment isn't the only way to handle signed integers, for instance. There's 1's compliment, and also (I don't know the term for this) a design that's more akin to how floats work; sign-bit that just dictates a positive or negative value separate from the actual value (e.g. 0x00000001 is +1 and 0x80000001 is -1). Defining signed integer overflow would require all but one of these designs to take a hit for every addition, subtraction, and multiplication to ensure language-defined results. Similarly, there's nothing to say floats in C++ must be IEEE-754; floating-point on at least some ARM chips is not IEEE-754-compliant actually, notably in regards to denormals (it doesn't support them, since they're easy to inadvertently generate, tough to protect against, and murder performance). Even x86 with SSE has an option to turn them off.
Should C++ define as much as it can, it would be restricted in what hardware it can reasonably work on, and also means it won't be able to adapt as future microprocessor designs create different low-level behaviors (at least not without breaking code that was previously compliant; sure that still sometimes happens now, but it would be many times worse the more you break previously-defined behavior).