Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between dereferencing pointer and accessing array elements

Tags:

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   
like image 962
bolov Avatar asked Feb 23 '14 18:02

bolov


People also ask

What is difference between pointer to array and array of pointers?

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.

What is the difference between referencing and dereferencing?

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.

What happens when you dereference a pointer to an array?

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.

How can a pointer be used to access individual elements of an array?

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.


2 Answers

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):

  • when declared int a[2]:
    • the compiler has for 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.
    • Accessing a[1] means:
      • retrieving that address,
      • adding the offset and
      • accessing the memory at this computed new address.
  • when declared int *b:
    • the compiler also has an address for b but this is the address of the pointer variable, not the array.
    • So accessing b[1] means:
      • retrieving that address,
      • accessing that location to get the value of b, i.e. the address of the array
      • adding an offset to this address and then
      • accessing the final memory location.
like image 90
3 revs Avatar answered Oct 30 '22 08:10

3 revs


// 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.

like image 40
ajay Avatar answered Oct 30 '22 07:10

ajay