A declaration says what a name means. A definition is a declaration that also creates the object, or supplies the function body (C17 6.7). C’s declaration syntax is built around how a name is used in an expression, which is why it reads inside-out.
Declaration syntax
declaration:
declaration-specifiers [init-declarator-list] ;
static_assert-declaration
declaration-specifiers (in any order):
storage-class-specifier typedef extern static auto register _Thread_local
type-specifier void char int float struct ... and so on
type-qualifier const volatile restrict _Atomic
function-specifier inline _Noreturn
alignment-specifier _Alignas ( ... )
init-declarator:
declarator [ = initializer ]
The specifiers apply to every declarator on the line. The * and
[] belong to each individual declarator, so a common error is:
int *a, b; /* a is a pointer to int; b is an int */
int *c, *d; /* both are pointers */
* binds to the name, not the typeSince C99 at least one type specifier is required: implicit int is gone.
Placing a storage-class specifier anywhere but first is allowed but obsolescent (C17 6.11.5).
The type-specifier keywords may appear in any order, so long unsigned int and
int long unsigned are the same type.
Storage classes are on the Storage and linkage page, and function specifiers on the Functions page.
Declarators
declarator:
[pointer] direct-declarator
pointer:
* [type-qualifier-list] [pointer]
direct-declarator:
identifier
( declarator )
direct-declarator [ [type-qualifier-list] [assignment-expression] ]
direct-declarator [ static [type-qualifier-list] assignment-expression ]
direct-declarator [ type-qualifier-list static assignment-expression ]
direct-declarator [ [type-qualifier-list] * ]
direct-declarator ( parameter-type-list )
direct-declarator ( [identifier-list] )
To read a declaration, start at the name and go outwards. Suffixes, [] and
(), bind more tightly than the prefix *. Parentheses change
the order.
| Declaration | Meaning |
|---|---|
int x; | x is an int |
int *x; | Pointer to int |
int x[3]; | Array of 3 int |
int *x[3]; | Array of 3 pointers to int |
int (*x)[3]; | Pointer to array of 3 int |
int f(void); | Function returning int |
int *f(void); | Function returning pointer to int |
int (*f)(void); | Pointer to function returning int |
int (*f[3])(void); | Array of 3 pointers to functions returning int |
int *const x; | Constant pointer to int |
void (*signal(int, void (*)(int)))(int); | Function taking an int and a pointer to a handler, returning a pointer to a handler |
A function may not return an array or a function, and an array may not have function
elements. It may return or hold pointers to them. typedef makes the harder
ones readable:
typedef void handler(int);
handler *signal(int, handler *); /* the same as the declaration in the table */
typedef for the function type removes the nestingAbstract declarators and type names
A type name is a declaration with the identifier left out. It is used by casts,
sizeof, _Alignof, compound literals and parameters that have no
name (C17 6.7.7).
| Type name | Meaning |
|---|---|
int * | Pointer to int |
int [3] | Array of 3 int |
int (*)[3] | Pointer to array of 3 int |
int (*)(void) | Pointer to function |
Static assertions
static_assert-declaration:
_Static_assert ( constant-expression , string-literal ) ;
static_assert ( constant-expression [, string-literal] ) ; (C23)
A static assertion checks a condition during compilation (C17 6.7.10). If the expression
is 0 the program does not compile, and the message is shown. It may appear anywhere a
declaration may. The message is optional from C23, and static_assert became a
keyword in that version. In C11 it is a macro in <assert.h>.
static_assert(sizeof(int) == 4, "int must be 32 bits");
_Static_assert(CHAR_BIT == 8, "bytes must be 8 bits");
Attributes (C23)
attribute-specifier:
[[ attribute-list ]]
attribute:
standard-attribute [( arguments )]
prefix :: identifier [( arguments )]
Attributes attach extra information to entities, statements and types, and may be
combined. A compiler that does not recognise an attribute ignores it. Vendor attributes use
a prefix, as in [[gnu::hot]].
| Attribute | Meaning |
|---|---|
deprecated, deprecated("reason") | Use of the entity is discouraged; a warning is encouraged |
fallthrough | A fall-through to the next case is intended |
maybe_unused | Suppresses warnings about an unused entity |
nodiscard, nodiscard("reason") | Encourages a warning if the return value is ignored |
noreturn | The function does not return |
unsequenced | The function is stateless, effectless, idempotent and independent |
reproducible | The function is effectless and idempotent |
Each standard attribute may also be written with double underscores around its name, such
as [[__nodiscard__]]. __has_c_attribute tests for support (see
Preprocessor).
Initialisers
initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
{ } (C23)
initializer-list:
[designation] initializer
initializer-list , [designation] initializer
designation:
designator-list =
designator:
[ constant-expression ]
. identifier
The rules are in C17 6.7.9:
- An object with automatic storage and no initialiser has an indeterminate value.
- An object with static or thread storage duration and no initialiser is set to zero: arithmetic types to 0, pointers to null, and each member and element recursively.
- An initialiser for an object with static storage duration must be a constant expression. C99 allowed automatic aggregates to take non-constant expressions.
- A scalar may be initialised with one expression, optionally in braces:
int x = {1};. - An aggregate initialiser fills members or elements in order. Any not given are set as
for static storage, so
int a[8] = {1};sets the first element to 1 and the others to 0. - Inner braces can be left out, and the initialisers then fill sub-objects in order. Compilers often warn about this.
- A
chararray may be initialised from a string literal. An array of unknown size takes its size from the initialiser. - A variable length array cannot have an initialiser in C17.
- In C23
= {}initialises any object, including a scalar, to zero.
int a[5] = {1, 2}; /* {1, 2, 0, 0, 0} */
int b[] = {1, 2, 3}; /* b has 3 elements */
char s[] = "hi"; /* {'h', 'i', '\0'} */
struct point { int x, y; };
struct point p = {1, 2};
struct point q[2] = {{1, 2}, {3, 4}};
struct point r[2] = {1, 2, 3, 4}; /* braces left out; the same result */
The order in which the expressions in an initialiser list are evaluated is unspecified in C17 (C17 6.7.9). Avoid side effects that depend on it.
Designated initialisers
Since C99 a designation names the element or member to set, so the order is free and members not named are set to zero (C17 6.7.9). After a designator, initialisation continues in order from the next element. A later designation overrides an earlier one for the same sub-object.
struct point p = { .y = 2, .x = 1 };
int a[6] = { [0] = 1, 2, [4] = 5 }; /* {1, 2, 0, 0, 5, 0} */
struct line { struct point from, to; };
struct line v = { .to.x = 3, .to.y = 4 };
int g[4][2] = { [1][1] = 7, [3] = {1, 2} };
Compound literals
A compound literal, (type){ initializer-list }, is an unnamed object made
with a type name and an initialiser (C17 6.5.2.5). It is an lvalue.
int *p = (int[]){1, 2, 3};
draw((struct point){ .x = 1, .y = 2 });
At file scope a compound literal has static storage. In a block it has automatic storage and lives until the block ends. In C23 storage-class specifiers may be written in a compound literal.