Home > database >  Is casting to (void**) well-defined?
Is casting to (void**) well-defined?

Time:07-14

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?

CodePudding user response:

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:

  • “If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.” This is generally not a problem; in common C implementations, the alignment requirements of void * and struct A * will be the same, and this is easily checked.

  • “Otherwise, when converted back again, the result shall compare equal to the original pointer.” This is all the paragraph tells us about the result of the conversion: It is a pointer you can convert back to struct A * to get the original pointer or its equivalent. It does not tell us the pointer can be used for anything else while it is in the void ** type.

  • “When a pointer to an object is converted to a pointer to a character type,…” This part of the paragraph does not apply, since we are not converting to a pointer to a character type.

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.

  • Related