Reputation: 4037
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
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 anint
.
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
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
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
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