Why do some experienced C developers avoid certain features of C99 and later standards?

Professional software developers who use C to develop source code that is portable across compilers will often find themselves avoiding certain newer features of the language, not from lack of knowledge or understanding, but because not all compilers implement all features. This can become even more of an issue in the embedded systems world, where a microcontroller vendor might provide a compiler implementation, and choose to freeze the feature set of the compiler at a level that lies somewhere in between C standards. Sometimes, these compiler implementations are never updated with new features, or might go for years or decades without a new feature added.

If source code portability across different C compiler implementations is important, and it often is important, the developer can’t just forge ahead and use every feature of the language documented in the standard, just because it’s there. Standards are valuable, but real-world compiler implementations and portability between them need to be considered carefully. Otherwise, you can incur extra project costs when it’s time to port to a different compiler implementation. As standardized as the C language is, it’s not exactly the same in every available and future C compiler implementation.

You can pretty much count on having the language features described in K&R2 (1988) and in the C89/90 standard. That’s a baseline. But C99 (1999) is where things can get a little murky. Some compiler implementations haven’t (and might never) implement some of the features described in C99.

For example, C99 standardized variable-length arrays (VLAs). Some compiler implementations support this feature, some don’t, and some support it in a non-standard, non-portable “extension” way. Some have chosen to never support this feature. In the C11 standard, the VLA feature was demoted to an “optional” feature, and this demotion will likely mean that many future implementations of C will not support the feature. You can’t be C99-compliant without implementing VLAs, but you can be C11-compliant without implementing VLAs. Those developers who counted on VLA support being there across all C compiler implementations will have some work to do, when they port their code to a compiler that doesn’t support this feature. Here we are, twenty years after C99 was ratified with VLAs as a required feature, and the VLA feature has already been relegated to the “optional feature” pile for eight years (at the time of this writing).

VLAs represent just one example. There are other C99 features which some implementations either provide, still don’t provide, or provide in a non-standard “extension” way. These features include (but are not limited to): flexible array members (FAMs), variadic macros, inline functions, intermingling declarations and executable statements, compound literals, long long int, complex number support, etc.

There are features presented in C11 (2011) which are and will be similarly treated by different compiler implementations, including (but not limited to): type-generic expressions, multi-threading support, removal of the now-deprecated gets function from the standard library, anonymous structs, anonymous unions, etc.

The C18 (2018) standard is really just a bug fix version of the standard, so no new features were introduced. But because C18 clarifies potentially ambiguous parts of C11, some C compiler implementations may differ, if their interpretation of C11 differed from the clarifications made in the C18 standard.

To help less experienced software developers, who might not have had to deal with the portability issue yet, many local coding standards explicitly list language features to be avoided, and some teams use static analysis tools to enforce this practice.

Bottom line: In real-world projects, the decision to use newer features of the C language needs to be considered carefully, if source code portability across compiler implementations is important. Software developers sometimes avoid newer features, to ensure their source code will compile on all current and future target implementations.