I remember an example where the difference between pointers and arrays was demonstrated.
An array decays to a pointer to the first element in an array when passed as a function parameter, but they are not equivalent, as demonstrated next:
//file file1.c int a[2] = {800, 801}; int b[2] = {100, 101};
//file file2.c extern int a[2]; // here b is declared as pointer, // although the external unit defines it as an array extern int *b; int main() { int x1, x2; x1 = a[1]; // ok x2 = b[1]; // crash at runtime return 0; }
The linker does not type-check for external variables so no errors are generated at compile time. The problem is that b
is in fact an array, but the compilation unit file2
is unaware of that and treats b
as a pointer, resulting in a crash when trying to dereference it.
I remember when this was explained it made perfect sense, but now I can't remember the explanation nor I can come up to it on my own.
So I guess the question is how is an array treated differently than a pointer when accessing elements? (because I thought that p[1]
is converted to (the assembly equivalent of) *(p + 1)
regardless if p
is an array or a pointer — I am obviously wrong).
The assembly generated by the two dereferences (VS 2013):
note: 1158000h
and 1158008h
are the memory addresses of a
and b
respectively
12: x1 = a[1]; 0115139E mov eax,4 011513A3 shl eax,0 011513A6 mov ecx,dword ptr [eax+1158000h] 011513AC mov dword ptr [x1],ecx 13: x2 = b[1]; 011513AF mov eax,4 011513B4 shl eax,0 011513B7 mov ecx,dword ptr ds:[1158008h] 011513BD mov edx,dword ptr [ecx+eax] 011513C0 mov dword ptr [x2],edx
A user creates a pointer for storing the address of any given array. A user creates an array of pointers that basically acts as an array of multiple pointer variables. It is alternatively known as an array pointer. These are alternatively known as pointer arrays.
I read about * referencing operator and & dereferencing operator; or that referencing means making a pointer point to a variable and dereferencing is accessing the value of the variable that the pointer points to.
Pointer to an array points to an array, so on dereferencing it, we should get the array, and the name of array denotes the base address. So whenever a pointer to an array is dereferenced, we get the base address of the array to which it points.
Access Array Elements Using Pointers In this program, the elements are stored in the integer array data[] . Then, the elements of the array are accessed using the pointer notation. By the way, data[0] is equivalent to *data and &data[0] is equivalent to data.
Thanks to the link provided by @tesseract in the comments: Expert C Programming: Deep C Secrets (page 96), I came up with a simple answer (a simple dumb down version of the explanation in the book; for a full academic answer read the book):
int a[2]
: a
an address where this variable is stored. This address is also the address of the array since the type of the variable is array.a[1]
means: int *b
: b
but this is the address of the pointer variable, not the array.b[1]
means: b
, i.e. the address of the array// in file2.c extern int *b; // b is declared as a pointer to an integer // in file1.c int b[2] = {100, 101}; // b is defined and initialized as an array of 2 ints
The linker links them both to same memory address, however since the symbol b
has different types in file1.c
and file2.c
, the same memory location is interpreted differently.
// in file2.c int x2; // assuming sizeof(int) == 4 x2 = b[1]; // b[1] == *(b+1) == *(100 + 1) == *(104) --> segfault
b[1]
is evaluated first as *(b+1)
. This means get the value at the memory location b
is bound to, add 1
to it (pointer arithmetic) to get a new address, load that value into the CPU register, store that value at the location x2
is bound to. So, the value at the location b
is bound to is 100
, add 1
to it to get 104
(pointer arithmetic; sizeof *b
is 4) and get the value at the address 104
! This is wrong and undefined behaviour and most likely will cause program crash.
There is a difference in how the elements of an array are accessed and how the values pointed to by a pointer are accessed. Let's take an example.
int a[] = {100, 800}; int *b = a;
a
is an array of 2
integers and b
is a pointer to an integer initialized to the address of the first element of a
. Now when a[1]
is accessed, it means get whatever is there at offset 1
from the address of a[0]
, the address (and the next block) to which the symbol a
is bound. That's one assembly instruction. It's as if some information is embedded into the array symbol so that the CPU can fetch an element at an offset from the base address of the array in one instruction. When you access *b
or b[0]
or b[1]
, you first get the content of b
which is an address, then do the pointer arithmetic to get a new address and then get whatever is there at that address. So the CPU has to first load the content of b
, evaluate b+1
(for b[1]
) and then load the content at address b+1
. That's two assembly instructions.
For an extern array, you don't need to specify its size.The only requirement is that it must match with its external definition. Therefore both the following statements are equivalent:
extern int a[2]; // equivalent to the below statement extern int a[];
You must match the type of the variable in its declaration with its external definition. The linker doesn't check for types of variables when resolving references of symbols. Only functions have the types of the function encoded into the function name. Therefore you won't get any warning or error and it would compile just fine.
Technically, the linker or some compiler component could track what type the symbol represents, and then give an error or warning. But there is no requirement from the standard to do so. You are required to do the right thing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With