int (*p)[4] , *ptr;
int a[4] = {10,20,30,40};
printf(\"%p\\n%p\\n%p\",&a,a,&a[0]);
p = &a ;
//p=a; gives error
//ptr = &a; gives error
ptr
"What
int (*ptr)[4]
really means and how is it different than*ptr
?"
First of all, we take a look at the declarations itself:
int * ptr
- ptr
is of type int *
- pointer to int
.
int (*ptr)[4]
- ptr
is of type int (*)[4]
- pointer to array of four int
.
The types are different.
I mean, if
p = &a = 0x7ff...
works, why notp = a = 0x7ff..
?
(Nitpicky side note: These expressions won´t get compiled, but I understand that this is just an example to illustrate the context.)
In C, expressions of array type are able to decay to pointers to the first element of the array.
Quote from the C18 standard, ISO/IEC 9899:2018:
"Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined."
Source: C18, §6.3.2.1/3
But since the &
operator is used at a
, a
does not decay to a pointer to the first element of a
. Instead, &
is applied to the array itself and yields a pointer to the whole array (type int (*)[4]
).
It is a syntactical type difference/mismatch, int *
vs. int (*)[4]
, although of course both point to the same address in memory.
The compiler is obliged to throw a diagnostic for any type mismatch as it is a syntax violation.
Of course, both have the same address, but the type incompatibility at the assignment makes the difference.
I tried to understand what
a
,&a
, and&a[0]
are.
In C arrays decay to pointers. All of those pointers reference the same memory location (first element of the array). The only difference is the type.
a
and &a[0]
have the type of the array elements (in this case int
)
&a
is of type pointer to array of elements type (in this case array of 4 integers).
Imagine pointers are laser pointers, with different colors (red for pointers to int, green for pointers to arrays, ...) and variables are things you can point to with the correct laser pointer, ie, you cannot use a green laser pointer to point to a char variable.
Ok, so you have int a[4]
an array (of 4 ints). Use a green pointer to point to it: int (*green)[4] = &a;
... you also have an int (a[0]
) which you can point to with a red pointer: int *red = &a[0]; /* in most contexts 'a' by itself is converted to "address of first element": &a[0] is the same as a */
.
Now ask your color-blind friend where the pointers point to :)
As far as your friend is concerned, they are equal and point to the same "place"... but you tricked your friend! Compilers are color-blind and don't like being tricked.
p
is a pointer to values of type int[4]
, i.e. a pointer to arrays of 4 integers each. Note that sizeof(*p)
is 4 times sizeof(int)
.
Now,
p = a
fails because a
decays to a pointer-to-int when assigned, while p
points to a different type. Read more about decaying: What is array to pointer decay?ptr = &a
fails because ptr
is actually a pointer-to-int; it doesn't have the same type as p
. Declaring multiple variables on the same line is often confusing, because not all syntax applies to everything you declare; better split up those definitions to separate lines.It's a matter of different types, and types is a concept that exists in the compiler but not in the compiled binary. That's why you get compiler errors even though the two pointer types actually point at the same address.
You can think of int (*p)[4]=&arr;
as a pointer to the whole array, whereas int* ptr=arr;
is a pointer to the first element in the array.
Normally when used in an expression, the array name "decays" into a pointer to the first element. This is what happens when we write int* ptr=arr;
- it is 100% equivalent to writing int* ptr = &arr[0];
.
Formally the "array decay" rule is defined in C17 6.3.2.1/3:
Except when it is the operand of the
sizeof
operator, or the unary&
operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.
As we can see, the &
operator is a special exception from the rule. Meaning that in case of &arr
, the arr
part does not decay. So we are expected to get a pointer to the array type and not just to the first element. That's where int (*p)[4]
fits in.
But of course, "a pointer to the whole array" will at the same time point at the address of the first item, because that's the address where the array starts. If we printf("%p\n", p)
, we will get the very same address no matter if we pass the array pointer or the pointer to the first element.
Array pointers are there to keep the language type system consistent. We sometimes run into them practically too, when we start to work with multi-dimensional arrays. If we for example define an int arr[2][3]
array, it is actually an array of 2 items, where each item is a int[3]
array. So what happens when we type arr
for this 2D array? As usual, the array decays into a pointer to the first item. And the first item is an array, so for the rule of array decay to stay consistent, it has to give a pointer to such an array of 3 integers. The type for such a pointer is int(*)[3]
.
Here are the basic rules:
For any1 type T
, you can have any of the following:
T *p; // p is a pointer to T
T *a[N]; // a is an array of pointer to T
T (*a)[N]; // a is a pointer to an array of T
T *f(); // f is a function returning pointer to T
T (*f)(); // f is a pointer to a function returning T
The postfix []
and ()
operators have higher precedence than unary *
, so an expression like *p[i]
is parsed as *(p[i])
. If you want to index into what p
points to, then you need to explicitly group the *
operator with p
, or (*p)[i]
. This precedence rule applies to both expressions and declarations.
Except when it is the operand of the sizeof
, _Alignof
, or unary &
operator, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T
" will be converted ("decay") to an expression of type "pointer to T
" and the value of the expression will be the address of the first element of the array. So given the declaration
int a[4] = {10, 20, 30, 40};
all of the following are true:
Expression Type Decays to Equivalent value
---------- ---- --------- ----------------
a int [4] int * &a[0]
&a int (*)[4] n/a &a[0]
*a int n/a a[0]
The expression a
has type "4-element array of int
" (int [4]
). a
is not the operand of the sizeof
, _Alignof
, or unary &
operators, so the expression "decays" to type "pointer to int
" and the value of the expression is the address of the first element. The result of this expression is exactly equivalent to &a[0]
.
The expression &a
has type "pointer to 4-element array of int
" (int (*)[4]
). In this case, a
is the operand of the unary &
operator, so the decay rule doesn't apply.
All of the expressions a
, &a
, and &a[0]
yield the same value (the address of the first element of a
), but the types of the expressions are different (which may affect how the value is represented). The type of a
and &a[0]
is int *
, but the type of &a
is int (*)[4]
.
Type matters for things like pointer arithmetic. Assume the following declarations:
int a[4] = {0, 1, 2, 3};
int *p = a;
int (*ap)[4] = &a;
Both p
and ap
initially point to the same address. However, the expression p + 1
will yield the address of the next int
object following whatever p
is pointing to (IOW, &a[1]
), while ap + 1
will yield the address of the next 4-element array of int
following a
.
This is exactly how array subscripting works - the expression a[i]
is evaluated as *(a + i)
. Given a starting address a
, offset i
objects (not bytes) and dereference the result.
And this is why you get the errors in some of your assignments - the types int *
and int (*)[4]
are not compatible. For one thing, they don't have to be represented the same way (although on any system you're likely to use they will be), and they behave differently when using pointer arithmetic.
Pointers to different types are themselves different types, and are not normally interchangeable.
T (*a)[N]
T
cannot be a function type, and for T (*f)()
T
cannot be a function or array type.