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.
void*
and then you can convert thevoid*
to astruct A*
after the function returns. To avoid the need for a temporary variable of typevoid*
you could return thevoid*
from the function similar tomalloc
andrealloc
. – Athanorout
parameters, problem solved. Now you havevoid * f(std::size_t);
even in C. And In C++ you could much improve the type safety withtemplate <typename T> T * f(); ... A *a = f<A>();
. Or just usenew A();
, that's what it's there fore. – Bifocals