1

Here is the code:

char s[8];
int i = 0;
void **p = &s;
for (; i < 8; ++i) {
    putchar( ((char*)(*p))[i] );
}

The code above could work but gave some rubbish chars. So what I did was simply initialize s[8]:

char s[8] = {0};

Then segmentation fault, but if I compile and run it using VC++, it works fine. Weird. Could anybody explain? Thanks!

UPDATE: So many guys said the codes above were stupid... this update is for you. What the original codes look like:

static void* 
copy_and_move(void **dst, int dsz, const void **src, int ssz) {
      const int sz = ssz > dsz ? dsz : ssz;      
      memcpy(*dst, *src, sz);
      return *dst + sz;
}

Then the calling codes:

char d[10], s[8];
copy_and_move(&d, sizeof(char) * 10, &s, sizeof(char) * 8);
blackball
  • 718
  • 1
  • 6
  • 19
  • 1
    why are you doing `((char*)(*p))[i]` instead of simply `s[i]`? – Dipto Jan 15 '14 at 09:18
  • 4
    Is your `*` key sticky? why cast a char to `char *`? Why declare a pointer as a pointer to a pointer? – Elias Van Ootegem Jan 15 '14 at 09:19
  • 6
    I'm continuously amazed by people who write expressions like that and yet are somehow surprised when they turn around and bite them on the derriere :-) – paxdiablo Jan 15 '14 at 09:19
  • Also avoid void unless you really know why you are using it. – Oragon Efreet Jan 15 '14 at 09:21
  • 1
    I simplified the original codes, SO, you saw the stupid codes above. You guys were all successfully missing the point. – blackball Jan 15 '14 at 09:25
  • The edited code isn't much better. There is no absolutely reason to pass a pointer-to-pointer to that function. – Lundin Jan 15 '14 at 09:59
  • And the bug in the edited code is the very same one too. – Lundin Jan 15 '14 at 10:03
  • Can u please write your compilation command? Are u sure u don't receive any warning? – Kyrol Jan 15 '14 at 10:07
  • @Kyrol A simple `gcc test.c` will even give a warning. But of course you should rather compile as `gcc test.c -std=c99 -pedantic-errors -Wall` or similar. – Lundin Jan 15 '14 at 10:25
  • 1
    @user1786323 you should read [this](http://stackoverflow.com/q/2528318/477168). In short: `s == &s`. – cYrus Jan 15 '14 at 13:24

7 Answers7

5

Let's see:

void **p = &s;

There, p points to s, that is, it contains the address of the first byte of s.

And it is a pointer-to-pointer, so *p is read as a pointer, it will use the first n bytes of s, being n equal to sizeof(void*) (4 or 8).

And then you are using that pointer *p to read bytes of memory...

Now, which bytes are in the memory of s that will be read as a memory address?

  • If the array is uninitialized: garbage, that in VC++ happens to look like a real pointer to who-knows-where. You read some random bytes.
  • If the array is initialized to {0], they will be all zeroes, so *p will be a NULL pointer and segfaults.
rodrigo
  • 94,151
  • 12
  • 143
  • 190
2

Your expression void **p = &s; is equivalent to void **p = &s[0];. Now subsequent *p gives you s[0], after which ((char*)(*p))[i] is s[0][i], which after initialization would be equivalent to *(((char*)0)+i), or, at first iteration, *(char*)NULL.

Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • The first point you made. Why you could confidently tell that without trying it first ?? – blackball Jan 15 '14 at 09:33
  • @user1786323 `printf("&s=%x\n",&s);printf("&s[0]=%x\n",&s[0]);` gives me idential addresses. Why would you say I didn't try it first without knowing it? – Ruslan Jan 15 '14 at 09:35
  • 2
    @user1786323 `&s` is equivalent to `s` which is equivalent to `&s[0]`. This is because of the way arrays "decay" into pointers. – Lundin Jan 15 '14 at 09:46
  • @Ruslan in this case yes, because the array is static. If the declaration of `s` was `char * s`, it was not equivalent. – Kyrol Jan 15 '14 at 15:41
2

void **p = &s; There is no rational reason to cast an array pointer into a pointer-to-pointer-to-void. There is no reason to use a void** at all.

So the code doesn't make any sense, this is what it actually does:

void **p = &s;

You tell the program that the address of an array should be stored in a pointer variable. That pointer variable assumes that the address given in turn points to another valid address. This is incorrect.

(char*)(*p)

Here you take the contents of the pointer-to-pointer and treats it as an address. But the contents of the pointer is the actual data of the array. You are invoking undefined behavior.

(char*)(*p)[i]

Here you are taking any random garbage address and treating it as if it was an array. This is also undefined behavior.

And since undefined behavior means that anything can happen, the program could crash or the program can seem to work okay. It is not meaningful to analyse why you get a certain program behavior when you invoke undefined behavior. Simply accept that your program contains bugs that you may or may not detect when you execute, depending on compiler and system.

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

So, as you say,

char s[8] = {0};
int i = 0;
void **p = &s;
for (; i < 8; ++i) {
    putchar( ((char*)(*p))[i] );
}

segfaults.

What you do is

  • declare a void ** pointing to your original char[]
  • use that void ** by continuously casting it.

I don't know if it hurts anyway, but it is unneededly complicated, leading to errors.

Let's have a closer look:

You do ((char*)(*p))[i], i. e. you dereference p - which points to your array - getting the value stored there as a pointer. This is dereferenced again in order to get the chars.

This is wrong.

Unless you are trying out something, I'd suggest

for (; i < 8; ++i) {
    putchar(s[i]);
}

If you want to learn what happens in the original case, I'd further suggest to break ti down into parts:

char s[8] = {0};
int i = 0;
void **p = &s;
char * base = *p;

printf("&s: %p\n", &s);
printf("p: %p\n", p);
printf("*p: %p\n", *p); // be aware that even this might be undefined...
printf("base: %p\n", base);
for (; i < 8; ++i) {
    putchar( base[i] ); // will lead to crash.
}

It is undefined behaviour to take a random bunch of bytes and treating them as a pointer.

Anything may happen here. In this case, the bytes consist of all NUL bytes (s is initialized with 0 all over its length), in your original case, the data were uninitialized and by luck containing a valid pointer (on the stack, this is likely to happen). Undefined nevertheless.

glglgl
  • 89,107
  • 13
  • 149
  • 217
0

Too many *: p holds the address of the array. *p dereferences that pointer, and interpretes the array contents as void *. This pointer contains rubbish, including that 0 you used for initialization. So you have to drop one level of *:

void *p = &s;
for (; i < 8; ++i) {
    putchar( ((char*)(p))[i] );
}
pentadecagon
  • 4,717
  • 2
  • 18
  • 26
0

As

void ** p = &s; /* p points to an array of 8 char. */

loses significant type information, apply it back by casting first, and dereference after, like so:

putchar( (*((char(*)[8]) p))[i] );
alk
  • 69,737
  • 10
  • 105
  • 255
0

As explain in a comment, this is why you have sigfault:

How come an array's address is equal to its value in C?

In short:

if you declare an array as static, char array[N], array and &array have the same address;

if you declare an array as pointer, char * array, array and &array have a different address;

Community
  • 1
  • 1
Kyrol
  • 3,475
  • 7
  • 34
  • 46