c++cpointerscastingpointer-conversion

Is casting to (void**) well-defined?


Suppose A is a struct and I have a function to allocate memory

f(size_t s, void **x)

I call f to allocate memory as follows.

struct A* p;
f(sizeof(struct A), (void**)&p);

I wonder if (void**)&p here is a well-defined casting. I know that in C, it is well-defined to cast a pointer to void* and vice versa. However, I am not sure about the case of void**. I find the following document which states that we should not cast to a pointer with stricter alignment requirement. Does void** have stricter or looser alignment requirement?


Solution

  • The conversion is not defined by the C standard, and, even if it were, code in f that assigned to it via the void ** type would not be defined by the C standard.

    C 2018 6.3.2.3 7 says a pointer to an object type may be converted to a pointer to a different object type. This covers (void **) &p, since &p is a pointer to the object p, and void ** is a pointer to the object type void *. However, this paragraph only tells us the conversion may be performed. It does not full define what the result is. It says:

    So, suppose the function f has some code that uses its parameter x like this:

    *x = malloc(…);
    

    Because the standard did not define what will happen if x is used as a void ** for any purpose other than converting it back to struct A *, we do not know what *x will do.

    A typical expectation is that *x will access the same memory p is in, but it will access it as a void * instead of as a struct A *. A technical problem here is that the C standard does not guarantee that a void * is represented in memory in the same way that a struct A * is represented in memory. As far as the standard is concerned, void * could use eight bytes while struct A * uses four bytes, or void * could use a flat byte address while struct A * uses a segment-and-offset address scheme. However, as with alignment, in common C implementations, different types of pointers have the same representation in memory, and this can be checked.

    But then we arrive at the aliasing rule. Even if void * and struct A * have the same representation in memory, C 2018 6.5 7 says:

    An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

    — a type compatible with the effective type of the object,

    The list continues with several other categories of types, and none of them match the struct A * type of p. That is, this paragraph in the standard tells us the object p shall have its stored value accessed (“accessed” in the C standard includes both reading and writing) only by an expression that has one of the listed types. The expression used to access p in *x = malloc(…); is *x, and its type is void *, and void * is not compatible with struct A *, and void * is also not any of the other types listed in the paragraph.

    So the code *x = malloc(…); breaks that rule. Violating a “shall” rule means the behavior of the code is not defined by the C standard.

    Some compilers support breaking this rule, when a switch is used to ask them to support aliasing objects through different types. Using such a switch prevents some optimizations by the compiler. In particular, given two pointers x and y that point to different types not matching the aliasing rule, then compiler may assume they point to different objects, so it can reorder accesses to *x and *y in whatever way is efficient because a store to one cannot change the value in the other.

    So, if you verify that void * and struct A * have the same representation and alignment requirement and that your compiler supports aliasing, then the behavior will be defined for the specific C implementation you check. However, it is not defined by the C standard generally.