Search code examples
cconstants

C: 'const' qualifier meaning in parameter list with typedef usage


How does the resolution of a parameter list work when a typedef and additional qualifier are used? Does it work like a textual replacement, similar to a #define?

Example:

typedef struct Parameters_t * Parameters_pot;
f(const Parameters_pot pars) 
f(Parameters_pot const pars) 

Does the 'const' qualifier in the parameter list in both function delarations relate to the data type, meaning the pointer is pointing to a constant structure? Or does the position of the 'const' matter and in the first function declaration the 'const' means that the structure object is constant and in the second declaration it means that the pointer is constant?


Solution

  • It is not text replacement. And that's exactly why hiding pointers or arrays behind a typedef is considered very bad practice. Both examples here are equivalent to:

    f(struct Parameters_t * const pars)

    Only since it was requested, here's an attempt at explaining the formal language lawyering:

    C17 6.7.8 states that a typedef has the form as typedef T type_ident; where type_ident is a type name "with the type specified by the declaration specifiers in T". Specifiers plural. To understand this we have to step back to 6.7 and 6.7.6 regarding "declarators" and "declaration-specifiers".

    6.7.6 says that every declaration can be said to consist of T D1 where T is the declaration specifiers and D1 the declarator.

    • A "declaration-specifier" is everything you may write in front of the identifier in a declaration, including storage class specifiers (static etc) and type qualifiers (const etc) and of course also type specifiers (int etc). Several of these may optionally exist in the same declaration.

      Except for storage class specifiers, which as a special case can never be combined with each other (6.7.1). And as it happens, typedef counts as a storage class specifier, so we can't have things like static inside a typedef.

    • A "declarator" contains the identifier as well as array notation etc. It may optionally contain a * for pointers. The important part here is that * belongs to the declarator.

      The formal syntax (6.7.6) goes as:

      declarator:
      pointeropt direct-declarator
      ...
      pointer:
      * type-qualifier-listopt

    Going back to typedef T type_ident;, then everything that is T belongs to the type, including any qualifiers, pointer syntax, array syntax etc that was used.

    Thus should we write something like const T, then const is applied to the whole type T. Now if we had 'T' as for example const int* "a pointer to const int" then const T will therefore create a const int* const, "a const pointer to const int".

    If T was a function, then it will attempt to declare a "const function" which is invalid. And so on.


    So for your example typedef struct Parameters_t * Parameters_pot;, the const is applied to the type struct Parameters_t *.

    Therefore f(const Parameters_pot pars) is equivalent to f(struct Parameters_t * const pars).

    f(Parameters_pot const pars) also has the same equivalent meaning, for the same reason as int const and const int are equivalent - the order in which declaration specifiers may appear isn't specified most of the time.