13

OK, I'm having trouble understanding pointers to pointers vs pointers to arrays. Consider the following code:

char s[] = "Hello, World";
char (*p1)[] = &s;
char **p2 = &s;
printf("%c\n", **p1); /* Works */
printf("%c\n", **p2); /* Segmentation fault */

Why does the first printf work, while the second one doesn't?

From what I understand, 's' is a pointer to the first element of the array (that is, 'H'). So declaring p2 as char** means that it is a pointer to a pointer to a char. Making it point to 's' should be legal, since 's' is a pointer to a char. And thus dereferencing it (i.e. **p2) should give 'H'. But it doesn't!

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Meta
  • 1,663
  • 2
  • 18
  • 29

3 Answers3

13

Your misunderstand lies in what s is. It is not a pointer: it is an array.

Now in most contexts, s evaluates to a pointer to the first element of the array: equivalent to &s[0], a pointer to that 'H'. The important thing here though is that that pointer value you get when evaluating s is a temporary, ephemeral value - just like &s[0].

Because that pointer isn't a permanent object (it's not actually what's stored in s), you can't make a pointer-to-pointer point at it. To use a pointer-to-pointer, you must have a real pointer object to point to - for example, the following is OK:

char *p = s;
char **p2 = &p;

If you evaluate *p2, you're telling the compiler to load the thing that p2 points to and treat it as a pointer-to-char. That's fine when p2 does actually point at a pointer-to-char; but when you do char **p2 = &s;, the thing that p2 points to isn't a pointer at all - it's an array (in this case, it's a block of 13 chars).

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
caf
  • 233,326
  • 40
  • 323
  • 462
  • Ah OK. I think I'm starting to understand now. Can you just clear up the part about s being a 'temporary, ephemeral value'? Wouldn't it be the same address everytime? – Meta Oct 28 '11 at 00:37
  • 1
    @Meta: I mean that the pointer that `s` evaluates to is not an addressable object, in the same way that `a + 1` is not (in standardese, it's not an lvalue). – caf Oct 28 '11 at 00:41
  • @caf, if `s` is not an addressable object, how come `&s` is not a compile error (but `&(a+1)` is)? – Shahbaz May 29 '12 at 16:56
  • 1
    @Shahbaz: `s` *is* an addressable object - it's an array - but the pointer value that `s` evaluates to *in most contexts* is not an addressable object. It so happens that `&s` is one of the contexts where `s` does *not* evaluate to a pointer to the first element - it remains a designator for the array, so `&s` gives the address of that array. The other context where `s` still designates the array itself is `sizeof s` (so this gives the size of the *array*, not of a pointer). – caf May 29 '12 at 22:46
  • @caf, it is starting to make sense. Array name is like a function name, `&` of it gives its address, but since without `&` it doesn't make sense, without `&` it also gives the address! The same way `func` and `&func` would be the same value. That is also why `char (*p)[]` should be used, similar to `char (*fptr)()`. Am I correct? – Shahbaz May 29 '12 at 23:20
  • 1
    @Shahbaz: It's very similar to the situation with functions, but not identical. The difference is that a function designator like `func` evaluates to a pointer to the function, which has the same type and value as `&func`. In the case of arrays, an array name like `s` evaluates to a pointer to *the first element* of the array, whereas `&s` is a pointer *to the array*. Now the first element of the array is necessarily located at the start of the array, so `s` and `&s` have the same value (eg. when converted to `void *`), but they have different *types*. – caf May 30 '12 at 00:18
2

From what I understand, 's' is a pointer to the first element of the array
No, s is an array. It can be reduced to a pointer to an array, but until such time, it is an array. A pointer to an array becomes a pointer to the first element of the array. (yeah, it's kinda confusing.)

char (*p1)[] = &s; This is allowed, it's a pointer to an array, assigned the address of an array. It points to the first element of s.

char **p2 = &s;
That makes a pointer to a pointer and assigns it the address of the array. You assign it a pointer to the first element of s (a char), when it thinks it's a pointer to a pointer to one or more chars. Dereferencing this is undefined behavior. (segfault in your case)

The proof that they are different lies in sizeof(char[1000]) (returns size of 1000 chars, not the size of a pointer), and functions like this:

template<int length>
void function(char (&arr)[length]) {}

which will compile when given an array, but not a pointer.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
1

Here's the sample that works, plus printouts of pointer addresses to make things simple to see:

#include <stdio.h>
char s[] = "Hello, World";
char (*p1)[] = &s;
char *p2 = (char*)&s;

int main(void)
{
   printf("%x %x %x\n", s, p2, *p2);
   printf("%x\n", &s);    // Note that `s` and `&s` give the same value
   printf("%x\n", &s[0]);
   printf("%c\n", **p1); 
   printf("%c\n", *p2);
}
Shahbaz
  • 46,337
  • 19
  • 116
  • 182
TJD
  • 11,800
  • 1
  • 26
  • 34