Declarations and Their Syntax in C

Declarations play a fundamental role in the C language. By declaring a variable or a function, essential information is provided to the compiler which can use it to check potential errors and to generate more efficient code.

In previous lessons we have seen examples of variable and function declarations but without going into too much detail. Starting from this lesson, we will see declarations and their syntax in more detail.

We will use the concepts of this lesson as a starting point to then tackle more advanced concepts such as storage classes and type qualifiers.

Declaration Syntax

A declaration in C language can be seen as a non-executable statement that provides information to the compiler regarding a particular identifier.

For example, when we write:

float x;

we are essentially telling the compiler that, within the current scope, the name x represents a variable of type float.

Similarly, when we write for example:

int sum(int a, int b);

we are declaring a function named sum that accepts two parameters of type int and returns a value of type int.

In general, a declaration has the following form:

specifiers declarators;

Where:

  • specifiers, also called declaration specifiers, are a list of keywords that describe the properties of the variable or function we are declaring;
  • declarators, also called base declarators, are a list of identifiers separated by commas. Declarators can also provide additional information about properties.

Let's analyze them separately.

Declaration Specifiers

Declaration specifiers can be divided into three types:

  • Storage Classes:

    also called storage classes, they specify the lifetime and visibility of an entity.

    In C Language there are five storage classes. Furthermore, only one storage class can be specified for each declaration:

    • auto
    • register
    • static
    • extern
    • thread_local
  • Type Qualifiers:

    they specify how the value of a certain variable can change over time. There are four of them and a declaration can contain from zero to more than one:

    • const
    • volatile
    • restrict
    • _Atomic

    In particular, the third, restrict, was introduced in the C99 standard, while the last, _Atomic, was introduced in the C11 standard.

  • Type Specifiers:

    they specify the data type of the variable or function we are declaring. There are several, including:

    • void
    • char
    • int
    • float
    • double
    • short
    • long
    • signed
    • unsigned

    Furthermore, it is possible to define custom data types using structures, unions and enumerations.

    Type specifiers can be combined with each other as we have already seen previously. Furthermore, the order in which they appear is not relevant, for example the declarations:

    int unsigned long x;
    unsigned long int x;
    

    are equivalent.

    Furthermore, we can also use type names specified with typedef.

In the C99 standard a special declaration specifier has also been added that is used exclusively for functions: inline. We will explore it in upcoming lessons.

In general, type qualifiers and type specifiers must always be inserted after the storage class and before the base declarators, so the sequence becomes:

storage_class type_qualifiers type_specifiers base_declarators;

However, between type qualifiers and type specifiers, the order is not relevant.

Declarators

As for base declarators, they can be:

  • simple names: in which case we are declaring a variable;

    int x;
    

    in this case the declarator is simply x, that is the name of the variable.

  • names followed by parentheses: in which case we are declaring a function;

    int sum(int a, int b);
    

    in this case the declarator is sum(int a, int b).

  • names followed by square brackets: in which case we are declaring an array;

    int numbers[10];
    

    in this case the declarator is numbers[10].

  • names preceded by an asterisk: in which case we are declaring a pointer;

    int *p;
    

    in this case the declarator is *p.

In a single declaration we can have multiple declarators separated by commas:

specifiers declarator1, declarator2, ..., declaratorN;

Each individual declarator can be followed by an initializer, that is an expression that assigns an initial value to the variable. This, obviously, only applies to variables.

The general syntax is as follows:

specifiers declarator = expression;

Summary of Declaration Syntax

Let's now summarize the general syntax of declarations in C language:

Definition

Declaration Syntax in C Language

The syntax of a declaration in C language is as follows:

storage_class type_qualifiers type_specifiers declarator1, declarator2, ..., declaratorN;

Where:

  • storage_class is a storage class, that is one of the keywords auto, register, static or extern;

    The storage class can be absent or only one can appear.

  • type_qualifiers is a list of type qualifiers, that is const, volatile or restrict;

    Type qualifiers can be absent or more than one can appear.

  • type_specifiers is a list of type specifiers, that is void, char, int, float, double, short, long, signed, unsigned or types defined with typedef;

  • declarator1, declarator2, ..., declaratorN are base declarators, that is names of variables, functions, arrays or pointers;

    Declarators can be followed by an initializer except in the case of functions:

    specifiers declarator1 = expression1, declarator2 = expression2, ..., declaratorN = expressionN;
    

    The initializer is an expression that assigns an initial value to the variable.

Examples

Let's put into practice the rules seen above by examining some examples.

Let's observe the following figure:

Declaration Example 1
Picture 1: Declaration Example 1

In this example we have:

  • A storage class: static;
  • A type specifier: double;
  • Three declarators: a and b which are simple variables and *p which is a pointer.

We have no type qualifiers.

Let's examine this second example:

Declaration Example 2
Picture 2: Declaration Example 2

In this example we have no storage class. However we have:

  • A type qualifier: const;
  • A type specifier: char;
  • A declarator: example[];
  • An initializer: = "Test";

In this case, example is a constant character array.

Let's analyze another example that has both storage class and type qualifier:

Declaration Example 3
Picture 3: Declaration Example 3

In this example we have:

  • A storage class: extern;
  • A type qualifier: const;
  • Two type specifiers: unsigned int; note that their order is irrelevant. We could also have written int unsigned;
  • A declarator: x.

To conclude, let's examine a last example of a function declaration:

Declaration Example 4
Picture 4: Declaration Example 4

In this case we have declared a function using:

  • A storage class: extern;
  • A type specifier that indicates its return type: double;
  • An identifier: square(double). This identifier indicates that the square function accepts one parameter of type double.

In Summary

In this lesson we have explored in depth the syntax of declarations in C language. In previous lessons we had only mentioned it.

In particular we have seen that:

  • A declaration is composed of specifiers and declarators;
  • Specifiers can be of three types: storage classes, type qualifiers and type specifiers;
  • Declarators can be names of variables, functions, arrays or pointers;
  • A declaration can contain multiple declarators separated by commas;
  • Each declarator can be followed by an initializer.

Although we have already studied type specifiers in previous lessons, especially when we studied data types in C, we have not, however, yet addressed the meaning and use of storage classes and type qualifiers. These topics will be covered in upcoming lessons.

In particular, in the next lesson we will address the concept of storage classes.