EmbeddedRelated.com

C99

Category: Standards

C99 is the widely used shorthand for the ISO/IEC 9899:1999 revision of the C programming language standard, which added fixed-width integer types, variable-length arrays, designated initializers, inline functions, single-line comments (previously supported by many compilers as an extension before being standardized), and other features over its C89/C90 predecessor. It remains one of the most widely targeted C standards in embedded firmware development.

In practice

C99's most practically important addition for embedded work is the `<stdint.h>` header, which defines fixed-width integer types such as `uint8_t`, `uint16_t`, `uint32_t`, and `int32_t`, along with related limit macros and other integer type definitions. On embedded targets where `int` can be 16 or 32 bits depending on the compiler and architecture, these types make register-width assumptions explicit and portable. `<stdbool.h>` (adding `bool`, `true`, `false`) and `<stddef.h>` refinements are similarly common in modern firmware.

Designated initializers are heavily used in embedded C to initialize peripheral configuration structs and hardware abstraction layer objects by field name rather than by position. This makes code more readable and less brittle when struct members are reordered. The `restrict` keyword, which promises a compiler that two pointers do not alias, can be meaningful in DSP or memory-copy routines on MCUs where the compiler cannot otherwise assume non-aliasing. `inline` functions provide a standard way to request inlining without relying on compiler-specific extensions like GCC's `__inline__`.

Most modern embedded toolchains, including GCC arm-none-eabi, Clang/LLVM, IAR EWARM, and Keil MDK (armcc/armclang), support C99 fully or nearly fully, though support can vary by toolchain version, selected language mode, and vendor dialect differences. Some older toolchains targeting 8-bit or 16-bit devices (certain PIC and 8051 compilers, for instance) have historically had incomplete C99 support, so it is worth verifying conformance before relying on features like variable-length arrays or complex number types. Variable-length arrays (VLAs), in particular, are frequently avoided in safety-critical and resource-constrained firmware because they allocate on the stack at runtime, making stack usage difficult to bound statically; they were made optional (implementation-defined) in C11.

C99 is often the baseline standard specified in embedded coding guidelines and static analysis tool configurations. If your project targets C99, note that C89-era idioms like declaring all variables at the top of a block are no longer required; C99 allows mixed declarations and code as one of its syntax changes, which can improve locality but may surprise developers accustomed to stricter C89 rules. For a broader perspective on writing clean, maintainable C for embedded systems, the EmbeddedRelated post "Data Hiding in C" covers module-level encapsulation techniques that work naturally within C99.

Frequently asked

What are the most important C99 features for embedded firmware?
The fixed-width integer types from `<stdint.h>` (e.g., `uint8_t`, `uint32_t`) and the boolean type from `<stdbool.h>` are the highest-impact additions for most embedded projects. Designated initializers for structs, `inline` functions, and `//` single-line comments are also widely used. The `restrict` qualifier matters in performance-sensitive data-movement or DSP code.
Should I use C99 or C11 for a new embedded project?
Either is reasonable. C11 adds `_Static_assert`, anonymous structs/unions, the `_Alignas`/`_Alignof` operators, and makes VLAs optional, all of which are useful in embedded work. Toolchain support for C11 is broad in GCC 5+ and Clang 3.1+. C99 remains a safe baseline if you need to support older or more constrained toolchains, such as some 8-bit or 16-bit device compilers.
Are variable-length arrays (VLAs) safe to use on microcontrollers?
Generally, VLAs are avoided in resource-constrained and safety-critical firmware. Because stack allocation happens at runtime, static analysis tools cannot easily compute worst-case stack depth, and a large VLA can silently overflow the stack. VLAs were made optional in C11, and coding standards such as MISRA-C:2012 prohibit them. Use a fixed-size array or dynamic allocation with explicit size checks instead.
Does enabling C99 mode change how GCC or Clang compiles for ARM targets?
Selecting a standard (`-std=c99`) primarily affects language parsing and which standard library declarations are exposed. It does not change code generation for most constructs. However, it does disable GNU extensions that conflict with the standard unless you use `-std=gnu99`, which adds GNU-specific extensions on top of C99. Many embedded projects use `-std=gnu99` or `-std=gnu11` to retain useful GCC extensions like `__attribute__` while targeting a C99 baseline.
Is C99 compatible with C++ toolchains or mixed C/C++ projects?
C99 and C++ are related but distinct languages. Most C99 code can be compiled as C++ with minor changes, but some C99 features, like designated initializers and implicit `int` casts from `void *`, are not valid C++. In mixed projects, C source files are typically compiled with a C99 or C11 flag while C++ files use a C++ standard flag. The EmbeddedRelated post 'C to C++: 5 Tips for Refactoring C Code into C++' covers practical considerations when moving between the two.

Differentiators vs similar concepts

C99 is often compared to C89/C90 (the previous dominant standard) and C11/C17 (successors). C89 lacks `<stdint.h>`, `//` comments, designated initializers, and `inline`. C11 adds `_Static_assert`, optional VLAs (where support is implementation-defined), atomic operations via `<stdatomic.h>`, and anonymous structs/unions; C17 is largely a bug-fix release over C11 with no new features. C99 is also occasionally confused with C++, which is a separate language; C++ has evolved independently (C++11, C++14, C++17, C++20) and is not a superset of C99 in all respects.