A less-experienced C coder sent me a question about pointers. It was in an area which I have seen proficient C coders stumble on. In my reply, I went on a few digressions to give a better understanding of C pointers. At one point, I gave the following example:
char *x1 = "123\n";
char x2[] = "456\n";
printf("%p %p", &x1, &x2);
&x1 is obvious - it evaluates to the address of the x1 variable (not the string it points at). I was all ready to proudly claim that &x2 is illegal and generates a syntax error. After all, x2 is an array, and referencing an array without an index evaluates to the address of the start of the array. What does it mean to take the address of an address? For that to make sense, the address of the array would need to be stored in a pointer variable. But there *is* no pointer variable holding the address of the x2 array, that address is just an ephemeral value generally held in a register. So &x2 should be illegal.
But it complied clean, not even a warning! I got out my trusty K&R 2nd edition (ANSI C) and it says, "The operand [to the & operator] must be an lvalue referring neither to a bit-field nor to an object declared as register, or must be of function type." Must be an lvalue, and x2 by itself is NOT an lvalue! (An lvalue can be assigned to: lvalue = rvalue;) Let's try the C99 spec: "The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier." Different wording, but still specifies an lvalue.
Finally, google led me to a stackoverflow where somebody asked the same question. The interesting reply:
In C, when you used the name of an array in an expression (including passing it to a function), unless it is the operand of the address-of (&
) operator or thesizeof
operator, it decays to a pointer to its first element.
That is, in most contextsarray
is equivalent to&array[0]
in both type and value.
In your example,my_array
has typechar[100]
which decays to achar*
when you pass it to printf.
&my_array
has typechar (*)[100]
(pointer to array of 100char
). As it is the operand to&
, this is one of the cases thatmy_array
doesn't immediately decay to a pointer to its first element.
The pointer to the array has the same address value as a pointer to the first element of the array as an array object is just a contiguous sequence of its elements, but a pointer to an array has a different type to a pointer to an element of that array. This is important when you do pointer arithmetic on the two types of pointer.
(Thanks to Charles Bailey for that answer back in 2010.)
Sure enough, x2 and &x2 both evaluate to the same pointer value, but x2+1 adds 1 to the address, and &x2+1 adds 5 to the address.
I can't find this mentioned in the formal C language specs I checked, but my experiments with the excellent site http://gcc.godbolt.org suggest that it is commonly implemented. A less-formal C book also mentions it.
So much for my newly-learned bit of C. Since I'm on the subject, here's a rewrite of the digression that triggered my investigation.
What does the following line do?
char *x1 = "123\n";
The quoted string causes C to allocate an anonymous 5-byte character array in memory and initializes it (at program load time) with 31 32 33 1a 00 (hex). I call it "anonymous" because there is no identifier (variable name) which directly refers to that array. All you have is the address of the array, which is what a quoted string evaluates to in an expression. That address is assigned into a pointer variable, x1.
So far, so good. Now:
char x2[] = "456\n";
This does *NOT* allocate an anonymous character array. It allocates a 5-character array and assigns the variable name"x2" and pre-initializes it with 34 35 36 1a 00 (hex). So the x2 variable (an array) is completely different from x1 (a pointer).
However, you can use the two variables in the same ways. For example, this will do what you expect:
printf("%s%s", x1, x2); /* prints two lines, "123" and "456" */
It is easy to think of x1 and x2 as more the same than they really are, but I think it helps to analyze it. Parameters passed to a function are expressions. The values actually passed are the results of evaluating those expressions. The above printf function is passed three expressions: "%s%s", x1, and x2. Here's how they are handled by C:
- "%s%s" - as before, the quoted string causes C to allocate an anonymous character array and inits it with the supplied characters. The quoted string evaluates to the address of that array, and that address is passed as the first parameter.
- x1 - x1 is a simple variable, so the expression evaluates to the content of the variable. In this case, that content happens to be the address of the string "123\n".
- x2 - x2 is *not* a simple variable, it is an array. Normally, an array should be accessed with an index (e.g. x2[1]). However, when an unindexed array name appears in an expression, C returns the address of the start of the array. I.e. x2 is the same as &x2[0]. So x2 passes the address of the string "456\n". But understand the mechanism is completely different; x1 passes the *content* of the variable x1 while x2 passes the *address* of the variable x2.
Similarly, consider this:
printf("%c%c\n", x1[1], x2[1]); /* print "25" */
Here again, the results are similar, but the mechanism is different.
- x1[1] - x1 is *not* an array, so what does x1[1] mean? It depends on x1's type. In this case, it is a character pointer, so C takes the address contained in x1 and adds 1*sizeof(*x1) to it, and fetches the character stored at that address. Since x1 points to type character, sizeof(*x1) evaluates to 1. So the expression x1[1] evaluates to the character stored at the address in x1 plus 1, which is the character '2'.
- x2[1] - this is very simple. Since x2 is an array, x2[1] simply evaluates to the contents of the second element in the array, which is the character '5'. Note that C does not have to do any pointer arithmetic, it just accessed the array directly.
In the above two examples, the pointer and the array can be thought of similarly. This true most of the time, and often leads to people forgetting that they are actually different things. For example:
printf("%d %d\n", sizeof(x1), sizeof(x2));
This prints "4 5" on a 32-bit machine and "8 5" on a 64-bit machine. The first number should be clear: x1 is a pointer, so sizeof(x1) should be the number of bytes in a pointer (4 or 8 depending on address length). But what about the 5? This is an interesting deviation from the patterns established previously. If we followed the previous pattern, sizeof(x2) should deconstruct as x2 evaluating to the address of the start of the x2 array. Then sizeof(x2) should return the size of the address. However, sizeof is *not* a normal C function, it's a language built-in operator (the parentheses are optional). It's operand is *not* interpreted as an expression, it's a type. So sizeof(x1) is not actually the size of the x1 variable, it is the size of the *type* of x1. Since the type of x2 is an array of 5 characters, sizeof(x2) evaluates to 5.
One more example of x1 and x2 being treated differently, which is the thing I just learned:
if (x1 == &x1) printf("x1==&x1\n");
if (x2 == &x2) printf("x2==&x2\n");
The output of the above two lines is the single line "x2==&x2". Note that this code also generates a warning on the second line since x2 and &x2 are pointers to different types (pointer to a byte v.s. pointer to an array of 5 bytes).
The moral? Although the mechanisms are different, an array and a pointer to an array can *usually* be treated the same in normal code. But not always. It's best to have a deep understanding of what's going on.
No comments:
Post a Comment