Worice
Worice

Reputation: 4037

Pointers dereference operator - Syntactic rules

I am unsure on the correct use of the star operator. Please consider the following example:

#include<stdio.h>

int main() {
   char  *w[3];      //Array of pointers
   w[0] = "Apple";
   w[1] = "Pear";
   w[2] = "Peach";

    printf("w[0] = %s, w[1] = %s, w[2] = %s\n", w[0], w[1], w[2]);   

    char **p = &w[0];
    char ***q = &p;
    printf("&w[0] = %p, *p = %s, p = %p, q = %p, *q = %p, **q = %s\n",
            &w[0],      *p,      p,      q,      *q,      **q);

return 0;
}

My expectations on the use of pointers:

int n = 3;
int *a = &n;    ->  a = &n  ->  *a = 3
int **b = &a;   ->  b = &a  ->  *b = &n  ->  **b = 3
int ***c = &b;  ->  c = &b  ->  *c = &a  ->  **c = &n  ->   ***c = 3

Above, p is a pointer to pointer. p holds the address of &w[0] and *p returns the value inside of &w[0]. Should not it be done with **p = "Apple? Same way, should not be ***q = "Apple, instead of **q?

I failed to find a resource to make me very clear the correct use of pointers-to-Npointers operators. In this sense, any suggestion would be highly appreciated. I hope I have been able to adequately communicate my question.

Upvotes: 3

Views: 155

Answers (4)

Frankie_C
Frankie_C

Reputation: 4877

Now I'll try to make some clarification about pointers, and to easy understanding I'll expose things in a simplified manner for now.

A pointer in C is a variable, as any other, which type is an address in memory.

Unfortunately having just an address that points somewhere in the process memory is not enough to correctly interact without the risk of corruption the other entities (generally variables) that lays adjacently to where the pointer points to. For this reason the language provide the qualification of the pointer allowing the user to specify to which object (type) it points, and permit to the compiler to select the way it uses to access the memory in compliance with the pointed object.

Said this, and going back to you original question, it should be clear now that a pointer is a variable that can hold the address of any type, basic or derived, existent in C language.

Now we go to the formal part. To interact with pointers we need some operators to work with them, basically an operator that resolves to the address of a variable, one that resolves to the value of the variable from the pointer and a declaration operator to "declare" a pointer.

Let start from the dereference operator *. This operator give-back the value (dereference) of the object from a pointer to it. But it is also the declarator for a pointer because is the most natural visual representation of a pointer. Look the following declaration:

int * p_int;

Reading the declaration following the C style, from right to left, we can say:

p_int is a variable that dreferenced give the value of an int.

It is a pointer.

Now if we have declared a variable p_int to be a pointer to an object of type int when we dereference it the compiler knows that to give back us an int value it must access the memory bytes starting from where the pointer points to and pack a number of bytes, requested for an int on the machine/compiler we are using, together to form an int.

Anyway a pointer, as any other variable, must be initialized or assigned to contain a valid address and then be usable for something. So we have to init/assign a value to our pointer that must be compatible with the type of object to which it points. If we have a variable:

int an_int;

of compatible type it could fit the scope. But a pointer holds the address of object so we cannot assign directly:

p_int = an_int;  //The compiler will trigger an error for incompatible type

to assign it we must get the address in memory of our variable. We need the unary operator & that give back the address of the object to which it is applied to.

p_int = &an_int;  //we assign to p_int the address in memory of an_int

Of course we can access again the value stored at the address pointed to by our pointer by dereferencing it:

int another_int = *p_int;

Before conclusion we must talk of a kind of "special handling" that C language reserves for arrays. In C the name of an array is automatically converted to the address of its first element (we'll see next which limitations exists for this in the standard). This means that the 2 lines following the array declaration are equivalent:

int array_of_int[10];
int *p_int  = array_of_int;
int *p_int1 = &array_of_int[0];

(even int *p_int1 = &array_of_int; is equivalent for the reasons we'll see below). Now we consider your example. The declaration:

char   *w[3];
char  **p = &w[0];
char ***q = &p;

Must be read as "w is an array of 3 pointers to char, p is a pointer to a pointer of int and q is a pointer to a pointer to a pointer of int.".

Lets decode. Each element of array w holds the address of a char, if in memory there is an array of char starting at that address, for the transitive property of what we said before about arrays, we can say that each w element holds the address of the first char of 3 char arrays of unspecified dimensions. Of course each of these arrays hold the 3 words "Apple", "Pear"and "Peach". In the declaration:

char  **p = &w[0];

we have created a variable that holds the address, in memory, where is saved the value of the pointer to char stored in the 0th element of the array w. Consider that w[0] would give the address where the string "Apple" starts, not the address of the 0th element where is saved the address where starts the string "Apple" (repetitive, but necessary to be clear!). So we use the unary operator & to obtain such an address.

The really interesting point is that the 2 asterisks in the declaration are declarative, not operators by themselves. To clarify consider:

char  **p;
p = &w[0];

This is perfectly equivalent to the previous, but in the first line we declare a pointer to pointer to char, in the second we assign it the address of the first element of w.

This should be enough to explain also the other parts of the question.

Now let be more more formal having a look to the C standard. We already said that in some cases arrays and pointers are automatically converted by the compiler. This is punctually stated in ISO/IEC 9899:2011 § 6.3.2.1 "Lvalues, arrays, and function designators" subsection 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. If the array object has register storage class, the behavior is undefined.

This explains also because using the & operator on an array operand resolves anyway to the address of the first array object.

Upvotes: 3

lockcmpxchg8b
lockcmpxchg8b

Reputation: 2303

Above, p is a pointer to pointer. p holds the address of &w[0]

You are correct that p is a pointer to a pointer.

You are not correct that p holds the address of &w[0]. The correct statement is that "p holds the address of w[0]."

The & is read as "address of", and it's what makes the assignment char **p = &w[0]; legal. Consider that

w[0] is a char *.
That means &w[0] is a char ** which is compatible with the type of p.

So p was assigned the address of w[0]. This is the same as saying "p points at w[0]". Hence *p is a reference w[0]...both *p and w[0] have type char * and point to the same string "Apple" in memory.

Note that the type of the literal string "Apple" is char [], because strings in C are implemented as arrays of chars. This coerces to char * transparently for initializing w.

(Thanks to @David Bowling for corrections)

Upvotes: 1

Korsarq
Korsarq

Reputation: 795

Simple example:

   p          w[0]
 -------     -------     -------------------- 
|       |   |       |   |      
|  0x05 |   |  0x00 |   |  'A'  p' 'p' 'l' 'e' 
 -------     -------     -------------------- 
 &p 0x07   &w[0] 0x06      0x00  ------------

*p will give you 0x00

**p will give you 'A'

Upvotes: 0

klutt
klutt

Reputation: 31296

printf("%s", p) expects p to be of type char *. If p is if type char ** you need to dereference it to get a char *.

Upvotes: 0

Related Questions