EmbCCdocs Repository

C Language Support#

This page describes the C language EmbCC accepts: the --std= values and what they change, the predefined macros that describe the language, and each feature of C89, C99, C11, C17 and C23 with its status. For every feature that is partial or refused it quotes the diagnostic EmbCC prints, so that the message can be searched for. It is for people porting C code to EmbCC and for people deciding which constructs a portable program can use. GNU and Clang extensions (statement expressions, __attribute__, builtins, pragmas) are in Extensions. Choices the standard leaves to the implementation, such as the signedness of char, are in Implementation-defined behavior. C++ is in C++ support.

Summary#

EmbCC compiles one C dialect:

  • the C17 language, which is C11 with the corrections of ISO/IEC 9899:2018;
  • the subset of C23 listed under C23;
  • the GNU extensions described in Extensions.

--std= does not select a different dialect, and __STDC_VERSION__ is 201710L whatever it names.

As a rule, a construct EmbCC does not implement is refused with an error that names it. The exceptions are the places where EmbCC accepts a program the standard requires it to diagnose, or gives a program a meaning other than the standard's. Check these first when porting code:

EmbCC is also stricter than the standard in one place that commonly affects existing code: an empty parameter list () always means "no parameters" (details). A non-void function that can reach its closing brace gets a warning that is on by default (details).

Features whose availability depends on the target (variable length arrays on AVR, long double, __int128, atomic sizes, thread-local storage) are in Target-dependent features.

The tables below use three statuses:

Status Meaning
Supported Implemented as the standard specifies.
Partial Implemented, with the restrictions given in the Notes column.
Not supported Refused with an error, quoted in the Notes column.

Diagnostics are quoted without their embcc: FILE:LINE:COL: error: prefix. The format is described in Diagnostics.

Selecting a standard#

--std=STANDARD#

GCC: -std=STANDARD.

Name a C standard. Each is compiled as it is: its keywords, its __STDC_VERSION__, C89's implicit int and C99's // comments, the unprototyped int f() before C23, and trigraphs under a strict standard before C23. The full rules are under --std=.

--std= value Standard __STDC_VERSION__
c89, c90, iso9899:1990, gnu89, gnu90 C89/C90 not defined
iso9899:199409 C94 199409L
c99, c9x, iso9899:1999, gnu99, gnu9x C99 199901L
c11, c1x, iso9899:2011, gnu11, gnu1x C11 201112L
c17, c18, iso9899:2017, iso9899:2018, gnu17, gnu18 C17 201710L
c23, c2x, iso9899:2024, gnu23, gnu2x C23 202311L
c2y, gnu2y C2y 202500L
any other value Error

The default is gnu17. A cNN value defines __STRICT_ANSI__. -ansi is --std=c89. Any other value is an error:

embcc: error: unknown standard '-std=c3x'

--pedantic and --pedantic-errors report, as a warning or an error, what the standard does not have: a later standard's feature, or a GNU extension.

See Diagnostics.

The C++ values (c++20, gnu++17, ...) are described in Invoking EmbCC and C++ support.

Keywords#

Because there is one dialect, the C23 keywords are recognized in every compilation. alignas, alignof, nullptr, static_assert, thread_local, typeof and typeof_unqual are reserved and cannot be used as identifiers. auto, bool, constexpr, false and true are recognized from context, so code that declares its own entity of one of those names keeps its meaning:

typedef int bool;        /* accepted: bool is this typedef     */
enum { false, true };    /* accepted: true is this enumerator  */
int constexpr = 1;       /* accepted: constexpr is a variable  */

The GNU keywords (asm, __typeof__, __attribute__, ...) are in Extensions.

Testing for features in source code#

  • #if __STDC_VERSION__ >= 202311L is false by default, although many C23 features are available; it is true under --std=c23. Test for the feature, or for __EMBCC__.
  • __has_c_attribute(NAME) is defined, but evaluates to 0 for every name, including the standard attributes EmbCC accepts.
  • __has_include works as specified.
  • None of the __STDC_NO_ATOMICS__, __STDC_NO_COMPLEX__, __STDC_NO_THREADS__ or __STDC_NO_VLA__ macros is defined. Where a target cannot compile one of these features, the compilation fails with the error given under Target-dependent features.

Predefined macros#

The macros the C standard defines. The target's macros (__x86_64__, __SIZEOF_INT128__, ...) are listed by --dump-predef and described in Targets. EmbCC also defines __EMBCC__ as 1; it does not define __GNUC__ when compiling C.

Macro Value
__STDC__ 1
__STDC_VERSION__ 201710L, for every --std= value
__STDC_HOSTED__ 1, also with --freestanding
__FILE__, __LINE__ Supported; #line changes both
__DATE__, __TIME__ Supported; SOURCE_DATE_EPOCH sets them, as in GCC. See Implementation-defined behavior.
__STDC_IEC_559__, __STDC_IEC_559_COMPLEX__ Not defined
__STDC_ISO_10646__, __STDC_MB_MIGHT_NEQ_WC__, __STDC_UTF_16__, __STDC_UTF_32__ Not defined
__STDC_ANALYZABLE__, __STDC_LIB_EXT1__ Not defined
__STDC_NO_ATOMICS__, __STDC_NO_COMPLEX__, __STDC_NO_THREADS__, __STDC_NO_VLA__ Not defined

C89 and C90#

Feature Status Notes
Declarations, expressions and statements Supported Except as listed in this table and under Feature notes.
Function prototypes Supported
Empty parameter list int f(); Partial Means "no parameters", as in C23. See Empty parameter lists.
Old-style (K&R) function definitions Not supported int add(a, b) int a, b; { ... } gives expected a parameter type before 'a'. C23 removed them.
Implicit int Not supported static x = 3; gives expected a type before 'x'. C99 removed it.
Implicit function declarations Not supported 'foo' is not declared in 'main' — for a call, add a prototype or define it first [E0001]. C99 removed them.
Reaching the } of a non-void function Supported A warning, on by default, except in main: control may reach the end of 'f', which returns int [--warn=return-type] [E0008]. See Functions that can reach their closing brace.
return; in a non-void function Supported The caller must not use the value. From C99 on, where it is a constraint violation: 'g' returns int; 'return' needs a value.
return x; in a void function Supported x is evaluated and dropped, with a warning that is on by default: returning a value from void 'f' [--warn=return-mismatch] (an error under --pedantic-errors, and from C99 on). A void x, return f();, is a GNU C form: see Smaller GNU C forms.
auto storage class Supported auto int x;
register storage class Partial Accepted on block-scope objects and on parameters, where it is a hint the allocator does not need. At file scope, which C does not allow: 'register' is not supported yet (see docs/manual/c-language.md). Taking the address of a register object is not diagnosed. Any other storage class on a parameter: a parameter can have no storage class but 'register'.
Storage class after the type, const static int t[], int static n, long typedef L Partial Obsolescent (C17 6.11.5), and -Wold-style-declaration (-Wextra) reports it, as in GCC. At block scope extern and typedef must come first: at block scope, write 'extern' first in the declaration.
const Supported See Const qualification.
volatile Supported
Scopes Partial Objects, functions and labels follow the standard's rules. Tags, typedef names and enumeration constants do not. See Scope of tags, typedefs and enumeration constants.
Structures, unions, bit-fields Supported A structure or union with no members is a GNU extension: size 0, as in GCC, and an error under --pedantic-errors.
Enumerations Supported A constant outside the range of int is accepted without a diagnostic and gives the enumeration a wider type, as in C23. See Enumeration constants.
Tentative definitions Supported A file-scope array of unknown size, int a[];, takes its size from a later declaration (int a[10];); if none gives one, it has one element, with a warning: array 'a' assumed to have one element (C17 6.9.2p5).
goto, labels, switch Supported
Adjacent string literal concatenation Supported A prefixed and an unprefixed literal concatenate: L"wi" "de" is L"wide".
Wide literals L"...", L'x' Supported
Multi-character constants 'ab' Supported GCC's value, reported by -Wmultichar as in GCC. See Implementation-defined behaviour. A wide one, L'ab', is refused: a wide character constant holds one character.
Trigraphs ??= ??( ... Not supported Not replaced: "??=" is a three-character string. C23 removed them.

C95 (Amendment 1)#

Feature Status Notes
Digraphs <: :> <% %> %: %:%: Not supported Read as separate punctuators. int a<:2:>; gives expected ';' before '<' [E0002]; a %:define line is not a directive.
<iso646.h> Supported
<wchar.h>, <wctype.h> Supported Provided by the C library; see Libraries.

C99#

Feature Status Notes
// comments Supported A backslash at the end of a // comment does not continue the comment onto the next line; see Translation phases.
Declarations mixed with statements; declarations in for Supported
long long, unsigned long long, LL and ULL suffixes Supported
_Bool and <stdbool.h> Supported
inline Yes See Inline functions.
restrict Supported Accepted. EmbCC does not use it for optimization.
Variable length arrays Partial See Variable length arrays. Not available on AVR.
Variably modified types: pointers to VLAs, VLA parameters int a[n][m], [*] Supported A definition evaluates its parameters' lengths on entry, the outermost too, though the parameter is a pointer: int a[i++] increments i. A length is an assignment-expression, so int a[i += 2] is one.
static and qualifiers in array parameters, int a[static 4] Supported The qualifiers apply to the pointer the parameter becomes: int a[const 4] is int *const a. static promises at least that many elements and changes no code. Outside a parameter: expected an expression, got 'static'.
Flexible array members Supported An initializer for the flexible member is accepted only on a static object, which is a GNU extension. On an automatic object: 'l' is not static, so its flexible array member 'd' cannot be initialized: the object has no room for the elements.
Designated initializers Supported
Compound literals Partial At file scope, an array compound literal that converts to a pointer is refused: int *p = (int[]){ 1, 2, 3 }; gives a scalar takes exactly one initializer. &(int){ 3 } at file scope, structure compound literals and array compound literals in a block work. Storage-class specifiers in a compound literal are C23; see the C23 table.
Non-constant initializers for automatic aggregates Supported
_Complex and <complex.h> Partial float, double and long double complex types. Static initializers are limited. See Complex types.
_Imaginary Not supported Not a keyword; a declaration that uses it is a syntax error.
Hexadecimal floating constants 0x1.8p1 Supported
Universal character names Partial In character constants and string literals. In an identifier one is refused, int caf\u00e9; gives character '\' is not supported yet; write the character in UTF-8 instead.
__func__ Supported
Variadic macros, __VA_ARGS__ Supported
Empty macro arguments Supported
_Pragma operator Supported
#pragma STDC FP_CONTRACT, FENV_ACCESS, CX_LIMITED_RANGE Partial Accepted and ignored.
va_copy Supported
Trailing comma in an enumerator list Supported
main returns 0 when it reaches } Supported
Integer division truncates toward zero Supported
__STDC_IEC_559__ and the other conditional feature macros Not supported Not defined; see Predefined macros.

C11#

Feature Status Notes
_Alignas, _Alignof, <stdalign.h> Partial See Alignment specifiers.
_Noreturn, <stdnoreturn.h> Supported The same as __attribute__((noreturn)).
_Static_assert Supported A failed assertion: static assertion failed: MESSAGE. A non-constant condition: _Static_assert needs a constant integer expression.
_Generic Supported See Generic selection. With no matching association and no default: no _Generic association matches type double.
_Atomic qualifier and _Atomic(T) specifier Partial Integer and pointer types only; sizes depend on the target. See Atomic types.
<stdatomic.h> Supported See Atomic types.
_Thread_local Partial Per-thread storage on the x86-64 and AArch64 ELF, EmbLinkOS and Linux targets; refused on macOS and Windows; one shared instance on Cortex-M, RISC-V, MIPS32 and AVR. See Target-dependent features. At block scope without static or extern: a block-scope __thread object must also be static: an automatic one is already private to the call.
Anonymous structures and unions Supported
char16_t, char32_t, u"...", U"...", u'x', U'x', u8"..." Supported <uchar.h> is provided by the C library.
max_align_t Supported Aligned as long double or long long, whichever is stricter: 16 bytes on x86-64, AArch64 and RISC-V, 8 on Cortex-M and MIPS32, 1 on AVR.
CMPLX, CMPLXF, CMPLXL Partial Defined as function calls, so they cannot initialize a static object. See Complex types.
Extended identifiers (C11 Annex D) Supported Written as UTF-8; a universal character name in an identifier is refused (see the C99 table). Any other non-ASCII byte outside a literal: byte 0xc3 is not part of a character C allows here (identifiers take UTF-8 letters, C11 Annex D).
Optional features: VLAs, complex types, atomics, threads Supported All four are provided, subject to Target-dependent features.

C17#

C17 adds no language features; it corrects C11. __STDC_VERSION__ has C17's value, 201710L.

C23#

Feature Status Notes
bool, true, false keywords Supported true and false have type bool. After #include <stdbool.h> they are the macros 1 and 0, of type int.
alignas, alignof, static_assert, thread_local keywords Supported
static_assert with one operand Supported A failed assertion: static assertion failed: (no message).
nullptr Partial Its type is void *, not nullptr_t. <stddef.h> does not declare nullptr_t: expected a type before 'nullptr_t'.
constexpr objects Partial Integer types only. See Constexpr objects.
auto type inference Partial At block scope only. See Type inference.
typeof, typeof_unqual Partial Not every expression form. See Type inference.
Attributes [[...]] Partial Not in every position. See Attributes.
[[deprecated]] Supported A use warns: 'g' is deprecated [--warn=deprecated-declarations]. A message argument is accepted and not printed.
[[fallthrough]] Supported Accepted. EmbCC does not warn about fall-through.
[[maybe_unused]] Supported Suppresses --warn=unused-variable, --warn=unused-function and, on a parameter, --warn=unused-parameter for the declaration.
[[nodiscard]] Supported Discarding the result warns: result of 'f' is discarded, and it is declared warn_unused_result [--warn=unused-result]. A message argument is accepted and not printed.
[[noreturn]], [[_Noreturn]] Supported
[[unsequenced]], [[reproducible]] Not supported Ignored with a warning: attribute 'unsequenced' is not one EmbCC knows, and is ignored [--warn=attributes].
__has_c_attribute Partial Defined, and evaluates to 0 for every attribute.
__has_include Supported
#elifdef, #elifndef Supported
#warning Supported Prints warning: #warning: TEXT.
#embed Partial Without parameters only. See Embedding binary data.
__has_embed Not supported Not defined. In #if it is an unknown identifier followed by parentheses: trailing junk in #if expression.
__VA_OPT__ Supported
true and false in #if Supported
Binary constants 0b1010 Supported Also in #if.
Digit separators 1'000'000 Supported In integer and floating constants, and in #if.
u8 character constants u8'a' Supported Of type unsigned char.
char8_t; u8"..." of type unsigned char[] Partial <uchar.h> declares char8_t under -std=c23, with mbrtoc8 and c8rtomb. u8"..." has C17's type, char[].
_BitInt(N), the wb and uwb suffixes Partial N up to 64, which is BITINT_MAXWIDTH; wider is refused: _BitInt(65) is wider than BITINT_MAXWIDTH, which is 64 here. Arithmetic in the type wraps at N bits, a _BitInt is not promoted to int, and conversions rank it by its width. Laid out and passed as the x86-64, AArch64 and RISC-V ABIs say, so calls between EmbCC and GCC agree. See Integers.
Enumerations with a fixed underlying type, enum E : unsigned char Partial The enumeration and its constants have the underlying type, and enum E : short; declares it ahead of its definition. A value the underlying type cannot represent is not diagnosed. See Enumeration constants. A non-integer type: an enum's underlying type must be an integer type.
Enumeration constants outside the range of int Partial The enumeration and its constants take a type that can represent every value. A value of 2^63 or more is read as negative. See Enumeration constants.
Empty initializer = {} Partial Zero-initializes any object except a VLA, which is refused: variable length array 'a' cannot be initialized. See Initializers.
Labels before declarations and at the end of a compound statement Supported
Unnamed parameters in a function definition Supported void on_tick(void *) { ... }. The parameter takes its argument's place and has no name in the debug information; --warn=unused-parameter does not report it.
Redefinition of a tag with the same content Not supported redefinition of 'P'
Storage-class specifiers in compound literals, (static int[]){ 1 } Not supported expected an expression, got 'static'
() means (void); K&R definitions removed Supported See Empty parameter lists.
Trigraphs removed Supported EmbCC never replaces trigraphs.
_Decimal32, _Decimal64, _Decimal128 Not supported expected a type before '_Decimal32'
_Float32, _Float64 Partial The same types as float and double. The f32 and f64 constant suffixes are refused: malformed floating constant.
_Float128 Partial The same type as long double where that is IEEE binary128. See Target-dependent features.
_Float16 Supported IEEE binary16 on every target, each operation rounded to it; the f16 constant suffix. Through ... it is refused. GNU's __bf16 (bfloat16) too. See Half-precision floating point.
_Float32x, _Float64x Not supported expected a type before '_Float32x'
unreachable() in <stddef.h> Supported Under -std=c23; it is __builtin_unreachable().
Width macros INT_WIDTH, INT8_WIDTH, ... Supported <limits.h> and <stdint.h>.
<stdbit.h>, <stdckdint.h> Supported Freestanding headers; see Libraries.
__STDC_VERSION__ of 202311L Supported Under --std=c23 or gnu23; the default, gnu17, is 201710L.
Identifiers per Unicode UAX #31 Not supported Identifiers use the C11 Annex D character ranges.

Later drafts#

The following features of the C2y working draft are not supported:

Feature Diagnostic
_Countof(a) '_Countof' is not declared in 'f' — for a call, add a prototype or define it first [E0001]
Octal prefix 0o17 malformed integer constant
Delimited escapes '\o{101}' unknown escape '\o' in a literal
Declarations in if and switch, if (int x = g(); x > 0) expected an expression, got 'int'
A type name as the controlling operand of _Generic expected an expression, got 'int'

Case ranges, case 1 ... 3:, are accepted as a GNU extension; see Extensions.

Target-dependent features#

The language features whose availability depends on the target. The targets are described in Targets: "x86-64" covers the ELF, EmbLinkOS and Linux triples, "AArch64" covers the ELF, EmbLinkOS and Linux triples, and "Cortex-M" covers every thumb* triple. Every compile for the Windows target also prints the --warn=windows-abi warning described there.

Feature x86-64 x86-64 macOS x86-64 Windows AArch64 Apple arm64 Cortex-M RV32 RV64 AVR
Variable length arrays Yes Yes Yes Yes Yes Yes Yes Yes No
long double format x87 80-bit, 16 bytes x87 80-bit, 16 bytes x87 80-bit, 16 bytes binary128, 16 bytes binary64, 8 bytes binary64, 8 bytes binary128, 16 bytes binary128, 16 bytes binary32, 4 bytes
long double arithmetic Yes Yes Not as a parameter or result Yes, in software Yes Yes No No Yes
float and double _Complex Yes Yes Yes Yes Yes Yes Yes Yes Yes
long double _Complex Yes Yes No Yes Yes Yes No No Yes
__int128 Yes Yes Not in a function signature Yes Yes No No Declarations and sizeof only No
_Float128 No No No Yes No No Declarations and sizeof only Declarations and sizeof only No
_Atomic operators 1 to 8 bytes 1 to 8 bytes 1 to 8 bytes 1 to 8 bytes 1 to 8 bytes 1 to 8 bytes; 8 by a lib/rt call 1 to 8 bytes; 8 by a lib/rt call 1 to 8 bytes 1 to 8 bytes, with interrupts masked
_Thread_local Per thread No No Per thread No One shared instance One shared instance One shared instance One shared instance

"One shared instance" means the object is placed in .tbss but addressed as an ordinary static object; see Targets.

The errors for the "No" entries:

Case Diagnostic
long double or _Float128 arithmetic on RISC-V, __int128 arithmetic on RV64 the RV64 backend cannot lower a 128-bit value yet (function f) [...] (RV32 on RV32)
long double in a signature on Windows long double in the signature of 'f' is not supported for a Windows target yet: there it travels by reference and returns through a hidden pointer, and EmbCC passes it on the stack by value
long double _Complex arithmetic on Windows passing long double is not supported for a Windows target yet: there it travels by reference and returns through a hidden pointer, and EmbCC passes it on the stack by value
__int128 in a signature on Windows __int128 in the signature of 'f' is not supported for a Windows target yet: EmbCC lowers its arithmetic to libgcc helpers whose arguments it places in the System V registers
__int128 on a 32-bit target or AVR __int128 does not exist on this target (it needs 64-bit registers; use long long)
_Float128 on x86-64 _Float128 is not supported on x86-64: `long double` here is x87's 80-bit extended format, not IEEE binary128, so it is not the same type
_Float128 where long double is not 16 bytes _Float128 is not supported on this target: it has no 128-bit floating-point type
An _Atomic object wider than the machine moves at once (16 bytes) an atomic access of 16 bytes is not one access on this target (it moves 8 at once): the halves could be split by an interrupt or another core
_Thread_local on macOS __thread is not supported for a Darwin target yet: Mach-O addresses a thread-local through a __thread_vars descriptor, which this writer does not emit
_Thread_local on Windows __thread is not supported for a Windows target yet: Windows reaches a thread-local through a _tls_index and a TLS directory this writer does not emit
va_arg of a structure on Windows va_arg of a struct is not supported for a Windows target yet

The text in square brackets names the internal operation that could not be lowered.

Feature notes#

Functions that can reach their closing brace#

A function with a non-void return type should end every path with a return statement. The standard allows control to reach the closing brace as long as the caller does not use the value, so EmbCC accepts it, with a warning that is on by default (--error=return-type makes it an error):

warning: control may reach the end of 'f', which returns int [--warn=return-type] [E0008]

The function returns 0 when control reaches the brace, if its result is an integer or a pointer, rather than whatever its return register held. A caller that uses the value then reads the same thing every time. A floating or aggregate result is undefined there.

main is the exception the standard makes: reaching its closing brace returns 0, without a warning.

A path also ends at an infinite loop, at a call to a function declared _Noreturn, [[noreturn]] or __attribute__((noreturn)), and at a call to exit, _Exit, abort, __builtin_unreachable or __builtin_trap. embcc --explain E0008 describes the rule; see Diagnostics.

Case labels in nested statements#

A case or default label can be anywhere in its switch statement's body: in a nested block, in an if, or inside a loop, which the switch then enters in the middle (Duff's device). This follows C11 6.8.4.2. A nested switch has its own labels. Duplicate values and a second default are errors at any nesting depth, and a label with no switch around it is an error ('case' outside of a switch).

Empty parameter lists#

A function declarator with an empty parameter list, int f();, declares a function with no parameters, as in C23, whatever --std= says. Code that relies on the older meaning, "parameters not specified", is refused:

Code Diagnostic
int f(); ... f(1); this call needs 0 arguments, got 1
int f(); int f(int a) { ... } conflicting declaration of 'f'
int (*fp)() = add; where add takes parameters initialization: cannot convert function * to function * without a cast

Scope of tags, typedefs and enumeration constants#

Objects, functions and labels have the scopes the standard gives them. A struct, union or enum tag, a typedef name and an enumeration constant are different: there is one namespace for each kind per translation unit, and a declaration in a block stays visible after the block ends. This has three consequences.

A name declared in a block can be used outside it, which the standard does not allow:

int f(void) { typedef int T; struct S { int a; } s = { 1 }; return s.a; }
int g(void) { T y = 2; struct S z = { y }; return z.a; }   /* accepted */

The same name declared with a different meaning in two blocks, or in a block and at file scope, is refused although the standard allows it:

Declarations Diagnostic
struct S { int a; }; at file scope, struct S { double d; } x; in a function redefinition of 'S'
typedef int T; in one function, typedef double T; in another redefinition of typedef 'T'
enum { A = 1 } in one function, enum { A = 2 } in another duplicate enumerator 'A'
enum { A = 1 }; at file scope, constexpr int A = 2; in a function 'A' is already a named constant (line 1); ...
int N; in a function, then enum { N = 3 }; in a block inside it 'N' would hide the local 'N' declared before it in this function; ...
enum { N = 3 }; in a function, and a variable or function N at file scope 'N', declared in a function body, has the name of the file-scope variable on line 2; ...

The last two would otherwise give the name two meanings in one program -- the constant in one place, the variable in another -- so they are refused rather than resolved either way. At file scope, an enumeration constant and a variable or function of the same name are the standard's redeclaration error:

error: 'g' redeclared as a different kind of symbol: it is also the function on line 2

A tagged structure or union, or any enumeration, cannot be defined inside a type name in an expression (a cast, sizeof, a compound literal):

error: define structs/unions at file scope (block-scope type definitions are not supported)
error: define enums at file scope (block-scope type definitions are not supported)

An untagged structure in a cast, (struct { int a; } *)p, is accepted.

A structure, union or enumeration tag declared in a block belongs to that block, as the standard specifies: two functions may each define union u, and an inner block's struct s { ... } declares a new type that hides an outer struct s until the block ends (CMSIS's cmsis_gcc.h defines union llreg_u inside each of its 64-bit DSP intrinsics). A local variable or a parameter may share its name with a typedef name or with an enumeration constant declared before it, and hides it, as the standard specifies. That includes an array bound:

enum { N = 3 };
int f(void) { int N = 5; int a[N]; return sizeof a; }   /* 20, a VLA of five */

Const qualification#

const is part of a type, at every level: const char * and char *const are different types from char * and from each other. An lvalue whose type is const-qualified, or a structure or union with a const member at any depth, cannot be modified. Each of these is an error, as the standard requires:

const int limit = 10;
void f(void) { limit = 11; }        /* assignment of read-only 'limit' */
void g(const int *p) { *p = 3; }    /* assignment of a read-only location */
void h(void) { limit++; }           /* increment of read-only 'limit' */
error: assignment of read-only 'limit' (its type is const int)
error: assignment of a read-only location (its type is const int)
error: assignment of struct S, which has a const member

A member of a const structure is const, and so is an element of a const array. Initialization is not assignment: a const object takes its value from its initializer.

Converting a pointer to a const-qualified type into a pointer whose pointed-to type is not const, without a cast, is diagnosed with the warning -Wdiscarded-qualifiers, which is on by default as in GCC:

warning: initialization discards the 'const' qualifier of const char * [--warn=discarded-qualifiers]

A cast removes const without a diagnostic. A const object with static storage duration is placed in a read-only section (.rodata). __auto_type and the value of an expression drop the qualifiers; typeof keeps them, and typeof_unqual drops them.

Enumeration constants#

An enumeration with a fixed underlying type, enum E : unsigned char, has that type, and so do its constants. A value that the underlying type cannot represent is refused:

error: enumerator 'B' is 256, which the underlying type unsigned char cannot represent

An enumeration without a fixed underlying type is int (two bytes on AVR) while every value fits int, and its constants then have type int. A constant outside the range of int, which C17 does not allow and C23 does, is accepted without a diagnostic. The enumeration then takes the first of these types that can represent every value, and, as C23 specifies, its constants take that type too:

  1. unsigned int, unsigned long, then unsigned long long, if no value is negative;
  2. long, then long long, otherwise.
enum big { HUGE = 0x100000005 };      /* unsigned long on x86-64: sizeof(enum big) is 8 */
long long f(void) { return HUGE; }    /* returns 0x100000005 */

Where this choice differs from GCC's and Clang's is described in Implementation-defined behavior. --enums=short is refused (see Targets).

A value of 2^63 or more, from an unsigned long long initializer, is that value: enum { X = 0xffffffffffffffff } is an unsigned long enumeration on the 64-bit targets, and X > 0. Two enumerations have no type that can represent every value, and are refused:

error: the enumeration's values run from -1 to 9223372036854775808, which no integer type holds
error: enumerator 'B' would be one past LLONG_MAX, which no integer type the enum can have holds

The first is a negative value together with one of 2^63 or more. The second is a constant without an initializer that follows LLONG_MAX (or, with ULLONG_MAX in the message, follows ULLONG_MAX).

Inline functions#

EmbCC follows C11 6.7.4p7. When every file-scope declaration of a function with external linkage in a translation unit says inline and none says extern, its definition there is an inline definition:

  • It provides no external definition. No symbol is defined for it in the object.
  • The optimizer may inline its calls. A call that is not inlined, every call at --opt=none, and any use of its address refer to the external definition, which is an undefined symbol in this object.
  • Exactly one translation unit of the program must provide the external definition. The usual way is a declaration with extern, or without inline, in one .c file that includes the header:
/* twice.h */
inline int twice(int x) { return 2 * x; }

/* twice.c -- the one external definition */
#include "twice.h"
extern inline int twice(int);

A static inline function is unaffected: it has internal linkage, and each unit that uses it has its own copy. A function that is main, a constructor or destructor, __attribute__((used)), or the target of an alias is always emitted.

GNU89 semantics apply instead with -fgnu89-inline, with --std=c89, --std=c90, --std=iso9899:1990, --std=gnu89 or --std=gnu90, and for a function with __attribute__((gnu_inline)). Under them a definition that says inline alone is an external definition, and one that says extern inline is used only for inlining and never emitted. See -fgnu89-inline.

Whether a call is inlined is decided by the optimizer; see Optimization.

Variable length arrays#

Supported: VLAs of any number of dimensions at block scope, VLA parameters, pointers to VLAs and [*] in prototypes. The size expression is evaluated once, where the declaration is reached; sizeof of a VLA is computed at run time; the storage is released when control leaves the block, including by break, continue and goto. This includes a typedef of a variably modified type, whose size is fixed where the typedef is declared: after int n = 3; typedef int row[n]; n = 10;, sizeof(row) is 12.

Refused:

Declaration Diagnostic
A VLA at file scope or as a structure member array size must be a constant expression here (a variable length array can only be a local variable or a parameter)
static int a[n]; static 'a' cannot have a variably modified type (int[*])
int a[n] = { 1 };, int a[n] = {}; variable length array 'a' cannot be initialized

A goto or switch that jumps into the scope of a VLA is not diagnosed, although the standard requires a diagnostic.

Complex types#

float _Complex, double _Complex and long double _Complex are supported, with all arithmetic operators. A bare _Complex is double _Complex. <complex.h> defines complex, I and _Complex_I and declares the library functions. long double _Complex depends on the target; see Target-dependent features.

GNU's integer complex types, _Complex int, short _Complex, unsigned long long _Complex and the rest, are supported too, and so is an integer imaginary constant, 2i, which is a _Complex int. * and / of two of them are computed in the integer type, as GCC computes them. A product is (ac - bd) + (ad + bc)i. A quotient uses GCC's scaled method, each division truncating: when |c| < |d|, with r = c / d, it is ((ar + b) + (br - a)i) / (cr + d); otherwise, with r = d / c, ((br + a) + (b - ar)i) / (dr + c). So (3+4i) / (1-2i) is -2+1i, as GCC has it; clang's formula gives -1+2i.

A complex object with static storage duration takes an initializer that EmbCC can fold. Otherwise:

error: a static complex initializer must be a constant that EmbCC folds: literals, casts, + and -, and scaling by a real

This rules out a product or quotient of two complex values, and CMPLX(1.0, 2.0), which <complex.h> defines as a call to a library function. At block scope both are accepted.

Half-precision floating point#

_Float16 (C23 Annex X) is IEEE 754 binary16 on every target: two bytes, aligned like a short, with an 11-bit significand. An operation on _Float16 operands has type _Float16, and each one is rounded to binary16. EmbCC computes it in float and rounds the result, which is exact, because a float's 24 bits are enough that rounding twice never differs from rounding once. So a program gets the answers of a machine with binary16 arithmetic, such as Arm's FEAT_FP16 or RISC-V's Zfh, on every target. FLT_EVAL_METHOD is 0.

GCC and clang on x86-64 evaluate instead in float, and round only at an assignment, a cast or a return. They may evaluate a constant in float too: 0.1f16 + 0.2f16 is 0x34cc with EmbCC and 0x34cd with GCC there. Where the two must agree, cast each intermediate result and constant to _Float16.

A constant takes the f16 or F16 suffix, and is rounded once from its digits. A conversion from double or long double is rounded once from that value, never through float.

__fp16 is Arm's storage format: the same binary16 bits, but a value read from one is a float, as a char read is an int. a + b of two __fp16 is a float, rounded once when it is stored, and through ... an __fp16 is a double. _Float16 and __fp16 are distinct types to _Generic.

__bf16 is bfloat16: the top half of a float, with its 8-bit exponent and an 8-bit significand, in two bytes. It works as _Float16 does, on every target: an operation on two of them, or on one and an integer, is a __bf16, each rounded to bfloat16; with a float it is a float; and with a _Float16 it is a _Float16, as in GCC and clang. A constant takes GCC's bf16 or BF16 suffix. A conversion to __bf16 from double, long double or an integer is rounded once: through a float, 2^24 + 2^16 + 1 would land on a half-way point and round down. The conversions are inline integer arithmetic, so no runtime routine is called. The exception is a 64-bit integer on AVR and RX, whose double is four bytes; there, __embcc_ll2bf and __embcc_ull2bf in lib/rt/half.c do it. The __BFLT16_MANT_DIG__ family of predefined macros describes it. A __bf16 travels between functions as a _Float16 does.

The predefined __FLT16_MANT_DIG__, __FLT16_MAX__ and the rest of that family are always defined. <float.h> defines FLT16_MANT_DIG and the other FLT16_ names when __STDC_WANT_IEC_60559_TYPES_EXT__ is defined before it is included.

A _Float16 argument or result travels as the target's calling convention passes one, so a function compiled by EmbCC can call or be called by one compiled by GCC or clang:

Target A _Float16 argument or result
x86-64, AArch64, ARM with -mfloat-abi=hard In the low 16 bits of the floating-point argument register
RISC-V with -mabi=ilp32f, ilp32d, lp64f or lp64d NaN-boxed in a floating-point register
RISC-V and LoongArch soft-float Sign-extended in an integer register
ARM soft-float, and targets whose convention has no _Float16 (AVR, MIPS, Xtensa, TriCore, RX, PowerPC, SPARC, ColdFire) Zero-extended in an integer register

A struct holding a _Float16 is passed like any other struct. On AArch64 a struct of two to four of them is a homogeneous aggregate, in h0 to h3. With the ARM hard-float convention, which also gives each member a floating-point register of its own, it is refused:

error: 'f': a struct holding a _Float16, passed or returned by value, is not supported yet with the hard-float calling convention, which puts each binary16 member in a floating-point register of its own; pass a pointer to it

The same refusal applies to RISC-V's hard-float conventions. A _Float16 passed through ... is refused on every target, because it is not promoted there:

error: 'printf': passing a _Float16 through '...' is not supported yet: it is not promoted there, and EmbCC does not yet place an unpromoted binary16 among the variable arguments; convert it to double, as va_arg will read it

The conversions between binary16 and the wider formats are calls to EmbCC's runtime, lib/rt/half.c. They use integer arithmetic only, so a target without a floating-point unit pays for no soft-float call there. On AArch64 each conversion is one fcvt instruction instead. The routines are named as libgcc names them where libgcc has them: __extendhfsf2, __truncsfhf2 and __truncdfhf2 on x86-64, and __gnu_h2f_ieee, __gnu_f2h_ieee and __gnu_d2h_ieee on ARM. On the other targets the names are EmbCC's own, __embcc_h2f and the rest, so a program linked against another runtime gets a link error naming the routine. On macOS the system's compiler-rt provides them.

Generic selection#

_Generic selects the association whose type is the type of the controlling expression after lvalue conversion, which removes the expression's own qualifiers and _Atomic. Types are compared exactly: long and long long are different types even on targets where they have the same size, plain char is neither signed char nor unsigned char, and const, volatile and _Atomic in a pointed-to type count:

_Generic((const char *)0, char *: 1, default: 2)   /* 2 */
_Generic(1, const int: 1, default: 2)              /* 2: the operand is an int */
_Generic(1L, long long: 1, long: 2)                /* 2 */

A selection in which two associations have the same type is refused, as the standard requires:

error: more than one _Generic association matches type int: two associations name the same type

Atomic types#

The operators on an _Atomic object (load, store, ++, -- and the compound assignments) are atomic for integer and pointer types. Other types are refused, with one of:

error: reading an _Atomic double is not supported: EmbCC makes the operators atomic for integers and pointers only
error: assigning to an _Atomic struct s is not supported: EmbCC makes the operators atomic for integers and pointers only
error: a compound assignment to an _Atomic double is not supported: EmbCC makes the operators atomic for integers and pointers only

The sizes each target supports are in Target-dependent features. On x86-64 and AArch64 the operators handle up to 8 bytes; a 16-byte _Atomic __int128 is refused with an atomic access of 16 bytes is not one access on this target (it moves 8 at once): ..., while the <stdatomic.h> functions and the __atomic_* builtins handle 16 bytes on those targets.

<stdatomic.h> is the C library's interface over the compiler's atomic builtins; see Libraries and Extensions.

Alignment specifiers#

_Alignas(N), _Alignas(type) and _Alignof(type) are supported on objects and structure members, in any order among the declaration specifiers. An automatic array, structure or union may have an alignment larger than the stack's own (16 bytes; 8 on Cortex-M), and gets it. The alignas and alignof spellings are keywords (see Keywords). The cases the standard constrains, and the ones EmbCC limits, are handled as follows:

Case Behavior
_Alignas(0) Accepted, with no effect, as the standard specifies.
An alignment that is not a power of two, _Alignas(3) Refused: _Alignas requires a constant power of two.
An alignment weaker than the type's, _Alignas(1) int Not diagnosed. The type's own alignment is kept.
_Alignas in a typedef or on a parameter Not diagnosed.
An automatic object aligned beyond the stack's alignment, _Alignas(32) int x; Supported, scalar or aggregate: its storage is carved from the stack at function entry and rounded up to the alignment, so its address has it at any call depth. A scalar so aligned lives in that storage rather than in a register.

_Alignof applied to an expression is a GNU extension; see Extensions.

Initializers#

  • Empty initializer. = {} zero-initializes the whole object: a scalar, an array, a structure or a union of any storage duration, a nested aggregate ({ {}, 1 }) and a compound literal ((struct p){}). A VLA cannot be initialized; see Variable length arrays.

  • Flexible array members. See the C99 table.

  • File-scope compound literals. See the C99 table.

  • Brace elision in an array of unknown size. When an initializer that omits inner braces could give the array a different number of elements depending on the types of its expressions, the declaration is refused rather than sized one way:

    error: cannot size 'g' from its initializer: the braces it leaves out read as 2 elements without the types and 1 with them; brace each element
    

    This happens, for example, with struct pt { int x, y; }; static struct pt g[] = { 1 ? 2 : 3, 4 };. Bracing each element, { { 1 ? 2 : 3, 4 } }, is accepted.

  • Braces around a scalar. One level is accepted, int q = { 4 };. More is refused: braces around a scalar initializer take one level.

Constexpr objects#

constexpr declares a named integer constant, at file scope or in a block, usable wherever an integer constant expression is:

constexpr int N = 4;
constexpr long long BIG = 5000000000;
int table[N];
Case Diagnostic
A non-integer type (floating, pointer, structure) constexpr 'd' of type double is not supported: EmbCC takes integer constants
A value the type cannot represent exactly constexpr 'c': 300 does not fit unsigned char, and C23 wants it exactly
A non-constant initializer constexpr 'k' needs a constant initializer; this one is not one
No initializer constexpr 'k' needs an initializer
Assignment to the object assignment target is not an lvalue
Taking its address, &k '&' needs a variable or *pointer

A constexpr name has no storage, which is why its address cannot be taken. typeof and auto cannot see its type (see Type inference).

Type inference#

typeof(...) and typeof_unqual(...) accept a type name or an expression. auto (C23) and __auto_type (GNU) declare an object whose type is that of its initializer. All three determine an expression's type before the rest of the semantic analysis, and accept these expression forms: identifiers of objects (including parameters and function pointers), integer, floating, character and narrow string constants, nullptr, casts, calls, subscripts, *, & of an object, . and ->, unary -, ! and ~, the binary arithmetic, bitwise, shift, comparison and logical operators, pointer arithmetic and ?:. The type follows the usual conversions: typeof(c + c) with char c is int, and typeof(arr) keeps the array type.

Other forms are refused: compound literals, sizeof and _Alignof, ++ and --, assignments, the comma operator, a function name, wide string literals, __real__ and __imag__, _Generic, statement expressions, and enumeration and constexpr constants (including true and false). The diagnostic is

error: typeof of an unsupported expression

for typeof, and for auto and __auto_type

error: __auto_type cannot see the type of this initializer

An assignment or comma expression inside typeof is a syntax error, for example expected ')' after typeof before ','.

auto infers a type only at block scope, for one declarator, with an initializer and no other specifiers before it except static:

Declaration Diagnostic
auto g = 3L; at file scope expected a type before 'auto'
auto x;, const auto x = 1; expected a type in this declaration, found 'auto'
auto a = 1, b = 2; expected ';' before ',' [E0002]

auto int x; is the C89 storage class and is accepted.

Attributes#

The [[...]] syntax shares its implementation with __attribute__((...)): [[gnu::NAME]] is the GNU attribute NAME, and the standard attributes map onto GNU ones (maybe_unused to unused, nodiscard to warn_unused_result, _Noreturn to noreturn). Any attribute namespace is dropped, so [[gnu::packed]] and [[packed]] are the same attribute. The GNU attributes and how each is handled are listed in Extensions.

[[...]] is accepted in these positions:

  • at the start of a declaration, at file scope and in a block;
  • after struct, union or enum: struct [[gnu::packed]] S { ... };;
  • before a parameter declaration: int f([[maybe_unused]] int a);
  • after a function declarator: void f(void) [[gnu::noinline]];;
  • before a statement, including the empty attribute statement [[fallthrough]]; and an empty list [[]].

It is refused after the declarator of an object, and after an array declarator, with expected an expression, got '[', and on an enumerator with expected '}' before '['. An attribute at the start of a declaration that declares no object or function, [[gnu::packed]] struct S { ... };, applies to nothing and is ignored without a diagnostic; put a type attribute after the struct keyword.

An attribute EmbCC does not know is ignored with a --warn=attributes warning, as for the GNU syntax.

Embedding binary data#

#embed "file" and #embed <file> expand to the bytes of the file as a comma-separated list of integer constants, for use in an initializer:

static const unsigned char logo[] = {
#embed "logo.bin"
};

The file is searched for as #include searches for a header: a quoted name first in the directory of the file that contains the directive, then in the include directories; an angle-bracketed name in the include directories only. An empty file expands to nothing.

Case Diagnostic
Any parameter (limit, prefix, suffix, if_empty, or a vendor one) #embed parameters (limit, prefix, suffix, if_empty) are not supported; ignoring one would embed the wrong bytes
A file name given by a macro, #embed FILE #embed needs a file name
A file that is not found cannot find the file to embed: "logo.bin"

__has_embed is not supported.

Headers#

The headers EmbCC ships are described in Libraries. The language-related declarations they do not provide:

Missing Standard Header
nullptr_t, unreachable() C23 <stddef.h>
char8_t C23 <uchar.h>
INT_WIDTH, BOOL_WIDTH, LLONG_WIDTH, ... C23 <limits.h>
INT8_WIDTH, SIZE_WIDTH, ... C23 <stdint.h>
FLT_NORM_MAX, DBL_NORM_MAX, ... C23 <float.h>

CMPLX, CMPLXF and CMPLXL are defined as function calls; see Complex types.

Preprocessor#

The preprocessor is integrated in embcc; -E writes its output. The command-line options that control it (-D, -U, -I, --system-include, the dependency options) are in Invoking EmbCC. The GNU directives and operators (#include_next, __has_include_next, __has_builtin, __has_attribute, #pragma pack, ...) are in Extensions.

Translation phases#

Phase Status Notes
Source character set Supported Source files are read as UTF-8. A leading byte-order mark is skipped.
Trigraphs Not supported Never replaced.
Line splicing Partial A backslash-newline is removed between tokens, in directives and inside identifiers and numbers. Inside a string literal or character constant it is an error (shown below the table). At the end of a // comment it does not continue the comment: the next line is compiled as code.
Comments Supported Each comment is replaced by one space.
Digraphs Not supported See C95.
Universal character names Partial In character constants and string literals only; see the C99 table.
String literal concatenation Supported

A backslash-newline inside a string literal or character constant gives:

error: unknown escape '\
' in a literal

Directives#

Directive Status Notes
#include "file", #include <file> Supported The search order is in Invoking EmbCC.
#include with a macro-expanded name, #include HEADER Supported The replacement must be one of the two forms, "name" or <name>: #include CMSIS_device_header.
#define, #undef Supported Redefining a macro with a body that differs only in white space is accepted with a warning, macro 'NAME' redefined; the standard allows it silently.
#if, #ifdef, #ifndef, #elif, #else, #endif Partial See Conditional expressions.
#elifdef, #elifndef Supported
#line DIGITS ["file"] Supported A missing or zero line number: #line needs a positive line number.
#error TEXT Supported An error: #error: TEXT.
#warning TEXT Supported A warning: #warning: TEXT.
#pragma Partial pack, once, push_macro, pop_macro and weak are acted on (see Extensions). Every other pragma, including the STDC pragmas, is ignored without a diagnostic.
_Pragma("...") Supported The same as the corresponding #pragma.
#embed Partial See Embedding binary data.
# alone (null directive) Supported
Any other directive In a group that is being compiled, an error: unknown directive '#ident'. In a group skipped by a conditional, ignored.

Conditional expressions#

#if and #elif support defined, the unary operators + - ~ !, the binary arithmetic, shift, relational, equality, bitwise and logical operators, and parentheses. Identifiers that are not macros evaluate to 0, except true, which evaluates to 1. Integer constants may be decimal, octal, hexadecimal or binary, with digit separators; character constants have the value they have in code. Differences from the standard:

Case Behavior
Unsigned arithmetic Every value is a signed 64-bit integer; a u suffix is ignored. #if -1 < 0u is true; the standard makes it false.

Macro expansion#

Object-like and function-like macros, the # and ## operators, variadic macros with __VA_ARGS__, __VA_OPT__, empty arguments and rescanning are supported. Stringizing (#) differs from the standard in its handling of white space:

Macro use EmbCC Standard
#define S(x) #x then S(a b) "a b" "a b"
#define V(...) #__VA_ARGS__ then V(a, b) "a,b" "a, b"

Runs of white space inside an argument, including a line break, are kept as written instead of becoming one space, and the space after each comma between variadic arguments is dropped.

Feature-test operators#

Operator Status Notes
defined NAME, defined(NAME) Supported
__has_include(<file>), __has_include("file") Supported #ifdef __has_include is true.
__has_c_attribute(NAME) Partial Defined (#ifdef __has_c_attribute is true), and 0 for every name.
__has_embed(...) Not supported Not defined; see the C23 table.