7

Is it legal to access a pointer type through a void **?

I've looked over the standards quotes on pointer aliasing but I'm still unsure on whether this is legal C or not:

int *array;
void **vp = (void**)&array;
*vp = malloc(sizeof(int)*10);

Trivial example, but it applies to a more complex situation I'm seeing.

It seems that it wouldn't be legal since I'm accessing an int * through a variable whose type is not int * or char *. I can't come to a simple conclusion on this.

Related:

Community
  • 1
  • 1
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174

3 Answers3

4

No. void ** has a specific type (pointer to a pointer-to-void). I.e. the underlying type of the pointer is "pointer-to-void"

You're not storing a like-pointer value when storing a pointer-to-int. That a cast is required is a strong indicator what you're doing is not defined behavior by the standard (and it isn't). Interestingly enough, however, you can use a regular void* coming and going and it will exhibit defined behavior. In other words, this:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *array;
    void *vp = &array;
    int **parray = vp;
    *parray = malloc(sizeof(int)*10);
}

is legitimate. Your original example won't even compile if I remove the cast and use apple llvm 4.2 (clang), due precisely to incompatible pointer types, i.e. the very subject of your question. The specific error is:

"Incompatible pointer types initializing 'void **' with an expression of type 'int **'"

and rightfully so.

WhozCraig
  • 65,258
  • 11
  • 75
  • 141
2

Pointer to different types can have different sizes.

You can store a pointer to any type into a void * and then you can recover it back but this means simply that a void * must be large enough to hold all other pointers.

Treating a variable that is holding an int * like it's indeed a void * is instead, in general, not permitted.

Note also that doing a cast (e.g. casting to int * the result of malloc) is something completely different from treating an area of memory containing an int * like it's containing a void *. In the first case the compiler is informed of the conversion if needed, in the second instead you're providing false information to the compiler.

On X86 however they're normally the same size and you're safe if you just play with pointers to data (pointers to functions could be different, though).

About aliasing any write operation done through a void * or a char * can mutate any object so the compiler must consider aliasing as possible. Here however in your example you're writing through a void ** (a different thing) and the compiler is free to ignore potentially aliasing effects to int *.

6502
  • 112,025
  • 15
  • 165
  • 265
  • I'm not really sure that pointer size has anything to do with it. AFAIK, `void*` is guaranteed to be able to hold the address returned by any call to malloc(). If you can store the return of malloc() in an `int*` then it shouldn't really matter if you do so through a `void**` instead. To me, it looks like a guarantee that `int*` and `void*` have the same size. Otherwise something as simple as `int* a = malloc(N);` would be undefined behavior. – Nikos C. Aug 31 '13 at 19:25
  • That's new to me - when did it become possible for two pointers in the same program to have different sizes? – ash Aug 31 '13 at 19:25
  • @ash Function pointers can have a different size than `void*`, and the return value of malloc() cannot be stored in a function pointer. – Nikos C. Aug 31 '13 at 19:26
  • @ash Its more common than you think, especially in C++. – WhozCraig Aug 31 '13 at 19:27
  • Still curious when this was introduced. In more than 20 years, I've never seen such a thing, but I've been away from C and C++ for a few years. – ash Aug 31 '13 at 19:29
  • The question is tagged as "C" btw, not "C++" – ash Aug 31 '13 at 19:30
  • @ash: for example there are architectures in which the minimum addressable size at the hardware level is e.g. 16 or 32 bit. However to avoid wasting spaces for text, pointer to chars are bigger to store the char inside the word. – 6502 Aug 31 '13 at 19:33
  • @ash I'm well aware of how the question is tagged. And if you ever programmed on mixed-model x86 code back in the day data pointers were commonly different sizes than code pointers, particularly in segmented architectures (of which there were several models on the x86 platforms at that time). – WhozCraig Aug 31 '13 at 19:34
  • Interesting; I've been lucky to avoid those situations. With all that said, the question of "legality" was raised. It's certainly legal, but it may not be wise. – ash Aug 31 '13 at 19:36
  • 2
    @NikosC.: no. If you write `int *x = (int *)malloc(...)` the compiler knows you're doing a conversion and can store the value correctly. `malloc` of course is required to return a pointer that is usable for any object. If instead you say `*p = malloc(...)` where `p` is a `void **` the compiler will perform a raw write operation thinking that the memory pointed by `p` is for a `void *`, thus performing no conversion even if that was needed. – 6502 Aug 31 '13 at 19:36
  • 1
    When you use malloc(), you don't cast the result. You can, but C guarantees that you don't have to. Ever. That means that if an `int*` is guaranteed to be able to hold the return of malloc(), which is a `void*`, then it stands to reason that `void*` and `int*` are guaranteed to have the same size. Unless I'm confused, of course :-P – Nikos C. Aug 31 '13 at 19:39
  • @ash It was in the Standard ever since it was born. –  Aug 31 '13 at 19:40
  • Also, ot would be worth not(h)ing that the exact reason for this being illegal ("undefined behavior", in standard's terminology) is that it violates the strict aliasing rule. (Google it if you don't know yet what it is.) –  Aug 31 '13 at 19:42
  • @NikosC.: You don't need to tell the compiler to do the cast because it's implicit. I like to put it anyway but it's a matter of style. The (IMO weak) reason it's considered "bad practice" in some circles is that if `malloc` wasn't declared properly and the compiler doesn't complain about undefined functions you could silence a mistake. Like I said `malloc` is required to return addresses valid for any type, so casting to an `int *` is safe... but this is totally irrelevant to the question. – 6502 Aug 31 '13 at 19:48
  • @NikosC - actually the compile is doing "what it is told" in both cases. So, it really doesn't "know" the right answer. That's why "C" is so easy to crash - because developers make mistakes all the time and the compiler can't tell. For example, when casing `malloc` to `int *`, it tells the compiler, "change this value to an `int *`". When assigned to another variable that's an `int *`, it knows not to perform any more conversion because the types are the same. `*p = malloc(...)` causes the compiler to write without conversion, correct. If the destination is the wrong sie, it will be bad. – ash Aug 31 '13 at 19:49
  • @6502 (an additional argument against casting is readability and redundance.) –  Aug 31 '13 at 19:49
  • @H2CO3: indeed any write done a through a `void *` must be considered as potentially modifying any object... but here the write is done through a `void **`. I'll edit the answer thanks. – 6502 Aug 31 '13 at 19:49
  • @6502 I'm not saying what you wrote is incorrect! It is correct. I was just tryimg to point out another reason why a cast is bad. –  Aug 31 '13 at 19:51
  • 1
    @6502 Yep, I was confused :-) You're right of course; if `void*` happened to be wider than `int*` on some platform, then malloc() would just have to make sure that it won't return a value that wouldn't fit in an `int*`, so when truncation happens (which is what a cast actually is, AFAIK) the result will still be correct. – Nikos C. Aug 31 '13 at 19:56
  • 1
    @Kevin: No. See n1124.pdf, page 36, point 26: "... All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.". – 6502 Aug 31 '13 at 20:36
0

Your code may work on some platforms, but it is not portable. The reason is that C doesn't have a generic pointer to pointer type. In the case of void * the standard explicitly permits conversions between it and other pointer to complete/incomplete types, but this is not the case with void **. What this means is that in your code, the compiler has no way of knowing if the value of *vp was converted from any type other than void *, and therefore can not perform any conversions except the one you explicitly cast yourself.

Consider this code:

void dont_do_this(struct a_t **a, struct b_t **b)
{
    void **x = (void **) a;
    *x = *b;
}

The compiler will not complain about the implicit cast from b_t * to void * in the *x = *b line, even though that line is trying to put a pointer to a b_t in a place where only pointers to a_t should be put. The mistake is in fact in the previous line, which is converting "a pointer to a place where pointers to a_t can be put" to "a pointer to a place where pointers to anything can be put". This is the reason there is no implicit cast possible. For an analogous example with pointers to arithmetic types, see the C FAQ.

Your cast, then, even though it shuts the compiler warning up, is dangerous because not all pointer types may have the same internal representation/size (e.g. void ** and int *). To make your code work in all cases, you have to use an intermediate void *:

int *array;
void *varray = array;
void **vp = &varray;
*vp = malloc(sizeof(int) * 10);
Michael Foukarakis
  • 39,737
  • 6
  • 87
  • 123