Search code examples
ctypedef

typedef pointer alias vs define pointer alias


When you declare a pointer to int:

int *a, b;

a is a pointer to int whereas b is an int (I've never really understood why this happens, I know is related to the comma but I used to say that the asterisk is binned to the variable name instead of data type). Anyway, if you:

#define intptr int*
intptr a, b

The result is the same. The fact that I find weird is why using typedef for the same thing changes the behavior?

typdef int* intptr 
intptr a, b;

In the last example I was expecting a to be an int* whereas b an int. Instead, they are both int*


For clarification, having the following code snippet I don't understand why I don't get the same warning for the d variable

#define intptr_def int*

typedef int* intptr_typ;

int main()
{
    int x;

    intptr_def a, b;
    intptr_typ c, d;

    a = &x;
    b = &x;     // Warning assignment to 'int' from 'int *' makes integer from pointer without a cast
    c = &x;
    d = &x;

    return 0;
}

Solution

  • A macro is a text substitution, nothing more. If you have

    #define intptr int*
    

    and write

    intptr a, b;
    

    then when the code is preprocessed, the intptr macro is expanded to

    int* a, b;
    

    which is processed exactly the same as

    int *a, b;
    

    and only declares a as a pointer; b is a regular int.

    A typedef name is an alias for a type - it’s not a text substitution like the preprocessor macro.

    typedef int *intptr;
    

    creates intptr as an alias for int * (pointer to int). So when you write

    intptr a, b;
    

    both a and b have type intptr (int *).


    Declarations in C have the basic structure

    declaration-specifiers declarators

    Declaration specifiers include type specifiers (int, float, char, etc.), type qualifiers (const, volatile), storage class specifiers (static, auto, register), and a few other specifiers related to function declarations/definitions.

    A declarator introduces the name of the thing being declared, along with information about that thing’s array-ness, pointer-ness, and function-ness. For example, in the declaration

    static volatile unsigned int a[10], *p;
    

    the declaration specifiers are static volatile unsigned int and the declarators are a[10] and *p. The type of each variable is fully specified by the combination of the declaration specifiers and the declarator. The type of a is "10-element array of static volatile unsigned int" and the type of p is "pointer to static volatile unsigned int".

    The idea is that the structure of the declarator matches the structure of the expression in the code. For example, suppose you have a pointer to an int named iptr, and you want to print the value in the object it points to, you’d write

    printf( "%d\n", *iptr );
    

    The type of the expression *iptr is int, so the declaration is written as

    int *iptr;
    

    You can also write it as

    int* iptr;
    

    or

    int*iptr;
    

    or even

    int      *      iptr;
    

    and all will be interpreted as

    int (*iptr);
    

    Whitespace doesn’t make any difference - the * is always part of the declarator, not the declaration specifiers. Declarators can get arbitrarily complex - for any sequence of declaration specifiers T, you can have

    T x;          // x is an object of type T
    T a[N];       // a is an array of T
    T *p;         // p is a pointer to T
    T f();        // f is a function returning T
    
    T *a[N];      // a is an array of pointer to T
    T (*p)[N];    // p is a pointer to an array of T
    T *f();       // f is a function returning pointer to T
    T (*p)();     // p is a pointer to a function returning T
    
    T *(*a[N])(); // a is an array of pointers to functions
                  // returning pointer to T
    

    and that just barely scratches the surface.