Example of declarations are:

int a; /* declaring single identifier of type int */

The above declaration declares single identifier named a which refers to some object with int type.

int a1, b1; /* declaring 2 identifiers of type int */

The second declaration declares 2 identifiers named a1 and b1 which refers to some other objects though with the same int type.

Basically, the way this works is like this - first you put some type, then you write a single or multiple expressions separated via comma (,) (which will not be evaluated at this point - and which should otherwise be referred to as declarators in this context). In writing such expressions, you are allowed to apply only the indirection (\\*), function call (( )) or subscript (or array indexing - ``) operators onto some identifier (you can also not use any operators at all). The identifier used is not required to be visible in the current scope. Some examples:

/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;

| Description|

—— | —— | 1| The name of integer type.| 2| Un-evaluated expression applying indirection to some identifier z.| 3| We have a comma indicating that one more expression will follow in the same declaration.| 4| Un-evaluated expression applying indirection to some other identifier x.| 5| Un-evaluated expression applying indirection to the value of the expression (*c).| 6| End of declaration.|

Note that none of the above identifiers were visible prior to this declaration and so the expressions used would not be valid before it.

After each such expression, the identifier used in it is introduced into the current scope. (If the identifier has assigned linkage to it, it may also be re-declared with the same type of linkage so that both identifiers refer to the same object or function)

Additionally, the equal operator sign (=) may be used for initialization. If an unevaluated expression (declarator) is followed by = inside the declaration - we say that the identifier being introduced is also being initialized. After the = sign we can put once again some expression, but this time it’ll be evaluated and its value will be used as initial for the object declared.

Examples:

int l = 90; /* the same as: */

int l; l = 90; /* if it the declaration of l was in block scope */

int c = 2, b[c]; /* ok, equivalent to: */

int c = 2; int b[c];

Later in your code, you are allowed to write the exact same expression from the declaration part of the newly introduced identifier, giving you an object of the type specified at the beginning of the declaration, assuming that you’ve assigned valid values to all accessed objects in the way. Examples:

void f()
{
    int b2; /* you should be able to write later in your code b2 
            which will directly refer to the integer object
            that b2 identifies */
    
    b2 = 2; /* assign a value to b2 */
    
    printf("%d", b2); /*ok - should print 2*/

    int *b3; /* you should be able to write later in your code *b3 */

    b3 = &b2; /* assign valid pointer value to b3 */

    printf("%d", *b3); /* ok - should print 2 */

    int **b4; /* you should be able to write later in your code **b4 */

    b4 = &b3;

    printf("%d", **b4); /* ok - should print 2 */

    void (*p)(); /* you should be able to write later in your code (*p)() */

    p = &f; /* assign a valid pointer value */

    (*p)(); /* ok - calls function f by retrieving the
            pointer value inside p -    p
            and dereferencing it -      *p
            resulting in a function
            which is then called -      (*p)() -

            it is not *p() because else first the () operator is 
            applied to p and then the resulting void object is
            dereferenced which is not what we want here */
}

The declaration of b3 specifies that you can potentially use b3 value as a mean to access some integer object.

Of course, in order to apply indirection (\\*) to b3, you should also have a proper value stored in it (see pointers for more info). You should also first store some value into an object before trying to retrieve it (you can see more about this problem here). We’ve done all of this in the above examples.

int a3(); /* you should be able to call a3 */

This one tells the compiler that you’ll attempt to call a3. In this case a3 refers to function instead of an object. One difference between object and function is that functions will always have some sort of linkage. Examples:

void f1()
{
    {
        int f2(); /* 1 refers to some function f2 */
    }
    
    {
        int f2(); /* refers to the exact same function f2 as (1) */
    }
}