9

In C, * is called the indirection operator or the dereference operator. I understand how it works when it is used in a statement. It makes sense to write *p or * p, considering that it is a unary operator.

However, sometimes in a declaration, a * is used.

void move(int *units) { ... }

or

int *p = &x;

It seems strange to me that an operator is used here. You cannot do things like

void move(int units++) { ... }

where ++ is also a unary operator. Is this * also the indirection operator, or does this * have a different meaning, where it says something about the type of the pointer? (If this is the case, I'm considering using for example int* units in declarations and int x = *p; otherwise for clarity.)

In this answer it is said that

The C standard only defines two meanings to the * operator:

  • indirection operator
  • multiplication operator

I've also seen people claiming that the * is actually part of the type. This confuses me.

1
  • 1
    In void move(int *units) { ... }, it is an indirection operator. Considered part of the type, it can also be written void move(int* units) { ... }, although I prefer the former style. You read both as "int pointer." See also stackoverflow.com/a/8911253 Aug 22, 2016 at 23:15

6 Answers 6

16

The smallest pieces of the C language are lexical tokens, such as keywords (e.g. int, if, break), identifiers (e.g. move, units) and others.

* is a lexical element called a punctuator (as are for example {}()+). A punctuator can have different meanings depending on how it is used:

A punctuator is a symbol that has independent syntactic and semantic significance. Depending on context, it may specify an operation to be performed (...) in which case it is known as an operator

In the C11 standard chapter 6.5 on expressions, * is defined as unary operator (section 6.5.3.2, for indirection) and as multiplicative operator (section 6.5.5), exactly as you've described. But * is only interpreted as operator according to the grammatical rules that make valid expressions.

But there's also a chapter 6.2 on concepts, that explains that pointers to a type are derived types. The section 6.7.6.1 about pointer declarators is more precise:

if, in the declaration ‘‘T D1’’, D1 has the form
* type-qualifier-list-opt D
and the type specified for ident in the declaration ‘‘T D’’ is ‘‘derived-declarator-type-list T’’, then the type specified for ident is ‘‘derived-declarator-type-list type-qualifier-list pointer to T’’.

This makes indeed * part of a derived type (* means there "pointer", but it needs always another type to say to what kind of thing it points to something).

Remark: There is no contradiction in this. You make such differentiated use of lexical language element everyday when you speak English: "a handle" designates something and "to handle" means doing something, two completely different grammatical usages of the same lexical element.

2
  • That means that this interpretation is just a common way to look at it (from the other answer): int *a this statement might be interpreted to read: declare a variable, named a, that when dereferenced is of type int. -When in fact the * has an Independent meaning in a declaration
    – Torm
    Aug 23, 2016 at 0:27
  • @Torm exactly !
    – Christophe
    Aug 23, 2016 at 6:22
7

In C, the indirection in a declaration is better read as part of the variable than part of the type. If I have:

int *a;

this statement might be interpreted to read: declare a variable, named a, that when dereferenced is of type int.

This is important when multiple variables are declared at once:

int *a, b;   (1)
int* a, b;   (2)

Both declarations are equivalent, and in both a and b are not the same types - a is a pointer to an int and b is an int. But declaraction (2) looks as though the "pointer type" is part of the type when it is actually the declaration of *a.

2
  • That's a very good point to bring this up, because ther is often a confusion between 1 and 2. However the type of a is "pointer to int", so even if the * only applied to a, it is definitely part of the type. You could for example use it in a typedef (which you could not do for an initializer).
    – Christophe
    Aug 23, 2016 at 6:35
  • Been looking for a good argument in deciding between the two declarations, this nailed it. I rarely declare variable as a list though but still, From now on i'll adopt the first form as being clearest in all cases. Thanks !
    – Newtopian
    Aug 23, 2016 at 13:29
4

To add to the other correct answers:

It's just a declaration expression that reflects potential usage expressions. IIRC, I think that Kernigan or Ritchie called it an experiment! Here's some marginally supporting text:

Back in the 70's, when Kernighan and Ritchie were writing The C Programming Language, they were pretty honest about how declarations can quickly become unreadable by the untrained eye:

C is sometimes castigated for the syntax of its declarations, particularly ones that involve pointers to functions. The syntax is an attempt to make the declaration and the use agree; it works well for the simple cases, but it can be confusing for the harder ones, because declarations cannot be read left to right, and because parentheses are over-used. [...]

(My emphasis.)

http://codinghighway.com/2013/12/29/the-absolute-definitive-guide-to-decipher-c-declarations/

So, yes, you are right, these are indeed the same operators applied to declaration expressions, though as you've observed only some of the operators make sense (e.g. ++ does not).

Such a star is indeed part of the type of the individual variable being declared; however, as other posters have noted, it does not (more precisely, it cannot) be carried over to other variables being declared at the same time (i.e. in the same declaration statement) -- each variable starts over with the same base type as the (eventual) target, and then gets its own (opportunity to apply or not) pointer, array, and function operators to complete its type.


This is why experienced programmers tend to prefer (1) int *p; vs. int* p; and (2) declaring only a single variable per declaration, even though the language allows more.

To add to that int (*p); is a legal declaration, these parens are simply grouping and in this simple case, and do nothing different than int *p. This is the same as saying an (non-declarative) expression (i) >= (0) is the same as i >= 0, as you can always legally add ()'s.

I mention that as another example, though, of why programmers prefer one form over the other, so consider int (*p); vs int(* p);. Perhaps you can see how the parenthesis, star, etc. apply to the variable not the base type (that is by adding all the permissible parenthesis).

1

In this function declaration:

void move(int *units);

the * is part of type, that is int*. It is not an operator. That is why many people would write it as:

void move(int* units);
1

Only the *, [], and () operators have any meaning in declarations (C++ adds &, but we won't go into that here).

In the declaration

int *p;       

the int-ness of p is specified by the type specifier int, while the pointer-ness of p is specified by the declarator *p.

The type of p is "pointer to int"; this type is fully specified by the combination of the type specifier int plus the declarator *p.

In a declaration, the declarator introduces the name of the thing being declared (p) along with additional type information not given by the type specifier ("pointer to"):

T v;     // v is a single object of type T, for any type T
T *p;    // p is a pointer to T, for any type T
T a[N];  // a is an N-element array of T, for any type T
T f();   // f is a function returning T, for any type T

This is important - pointer-ness, array-ness, and function-ness are specified as part of the declarator, not the type specifier1. If you write

int* a, b, c;

it will be parsed as

int (*a), b, c;

so only a will be declared as a pointer to int; b and c are declared as regular ints.

The *, [], and () operators can be combined to create arbitrarily complex types:

T *a[N];      // a is an N-element array of pointers to T
T (*a)[N];    // a is a pointer to an N-element array of T
T *(*f[N])(); // f is an N-element array of pointers to functions 
              // returning pointer to T

T *(*(*(*f)[N])())[M]  // f is a pointer to an N-element array of pointers
                       // to functions returning pointers to M-element
                       // arrays of pointers to T

Notice that *, [], and () obey the same precedence rules in declarations that they do in expressions. *a[N] is parsed as *(a[N]) in both declarations and expressions.

The really important thing to realize in this is that the form of a declaration matches the form of the expression in code. Going back to our original example, we have a pointer to an integer named p. If we want to retrieve that integer value, we use the * operator to dereference p, like so:

x = *p;

The type of the expression *p is int, which follows from the declaration

int *p;

Similarly, if we have an array of pointers to double and we want to retrieve a specific value, we index into the array and dereference the result:

y = *ap[i];

Again, the type of the expression *ap[i] is double, which follows from the declaration

double *ap[N];

So why doesn't ++ play a role in a declaration like *, [], or ()? Or any other operator like + or -> or &&?

Well, basically, because the language definition says so. It only sets aside *, [], and () to play any role in a declaration, since you have to be able to specify pointer, array, and function types. There's no separate "increment-this" type, so there's no need for ++ to be part of a declaration. There's no "bitwise-this" type, so no need for unary &, |, ^, or ~ to be part of a declaration either. For types that use the . member selection operator, we use the struct and union tags in the declaration. For types that use the -> operator, we use the struct and union tags in conjunction with the * operator in the declarator.


  1. Of course, you can create typedef names for pointer, array, and function types, like
    typedef int *iptr;
    iptr a,b,c; // all three of a, b, and c are pointers to int
    
    but again, it's the declarator *iptr that specifies the pointer-ness of the typedef name.

1

The C standard only defines two meanings to the * operator:

  • indirection operator
  • multiplication operator

It's true that there are two meanings for the * operator. It's not true that those are the only meanings of the * token.

In a declaration like

int *p;

the * is not an operator; it's part of syntax of a declaration.

C's declaration syntax is intended to mirror its expression syntax, so the above declaration can be read as "*p is of type int", from which we can infer that p is of type int*. However, this is not a firm rule, and it's not stated anywhere in the standard. Instead, the syntax of declarations is defined on its own, separately from the syntax of expressions. That's why, for example, most operators that can appear in expressions cannot appear with the same meanings in declarations (unless they're part of an expression that's part of the declaration, like the + operator in int array[2+2];).

A case where the "declaration mirrors use" principle doesn't quite work is:

int arr[10];

where arr[10] would be of type int if it existed.

And in fact there is yet another use of the * token. An array parameter can be declared with [*] to indicate a variable-length array. (This was added in C99.) The * is neither a multiplication nor a dereference. I suspect it was inspired by shell wildcard syntax.

2
  • A bigger place the "declaration follows use" concept breaks down is with pointer initialization. The effect of a declaration int *p = 0; looks similar to usage *p = 0;` but the meanings are very different.
    – supercat
    Feb 6, 2018 at 19:57
  • @supercat: Sure. the initialization always matches the type of the object (in this case int*). Feb 6, 2018 at 21:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.