- What does it mean by "interchangeability"? Does it say the argument and the return values of a function
void* Func(void*)
can both be char*
?
Yes, that is what it says, but it is non-normative text that conflicts with the normative text of the standard. Let’s discuss question 2 and then come back to this.
- If so, is it an implicit conversion made by the compiler?
No, not in the situations intended to be addressed by this note.
If there is a visible declaration of void *Func(void *);
, and you execute:
char *p = something;
char *q = Func(p);
then the argument p
is converted void *
and the returned value is converted to char *
. But these conversions occur as part of the normal operations of function calls and assignments; they have nothing to do with the types having the same representation or being interchangeable. For example, if you executed code like the above but with int *
instead of char *
, the conversions would occur between int *
and void *
even if they do not have the same representations and are not interchangeable. The argument conversion is made because the compiler knows the parameter type of Func
, so it performs the conversion as required by the rules for function calls, and the assignment conversion is made because the compiler knows the type of the destination of the assignment, so it performs the conversion as required by the rules for assignment.
However, suppose we have this code:
char *Func(char *);
char *p = something;
char *q = Func(p);
but Func
is in fact defined in its library source code as void *Func(void *);
. Then the rule in C 2018 6.2.5 281 applies. In the calling code, the compiler is told the parameter and the return type are char *
, so no conversion is performed in either case. When passing the char *
argument, the compiler passes exactly the bytes that represent a char *
. In the receiving function, the code expects a void *
. Since the bytes representing a char *
are exactly the same as the bytes representing a void *
, with the same meaning in regard to the represented address, this works: The function receives the bytes it expects to receive, with the intended meaning. Similarly, when the function returns the bytes for a void *
and the calling code interprets those bytes as a char *
, it works because the bytes are the same, with the same meaning.
Getting back to question 1, this example where Func
is called using the type char *Func(char *)
but is defined using the type void *Func(void *)
violates the normative part of the C standard. C 2018 6.5.2.2 6 says:
If the function is defined with a type that includes a prototype, and… the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.
char *
is not compatible with void *
, so the behavior is not defined by this rule. However, if the calling code is in one translation unit and the called code is in another translation unit, and no information about the calling code or the called function (notably no type information) is passed between translation units except for linking the name to the function, then it is impossible for the C implementation to distinguish our example code from code in which the function is called using a type compatible with its definition. In particular, the fact that char *
has the same representation as void *
means that the result of compiling the calling code must be identical whether it uses char *Func(char *)
or void *Func(void *)
(given the caveat that no type information ais passed between translation units), and it means that the result of compiling the function definition must be identical whether it is defined using char *
or void *
. In other words, a rule of the C standard says the behavior is not defined, but it is logically impossible in this situation for the compiler to compile the example code differently from the code with defined behavior.
I conjecture that this note in the standard may have been the result of the committee, or at least one or more members of it, wanting to say that, at least in some senses, char *
could be used in place of void *
and vice-versa, but that the committee did not have the time or motivation or other opportunity to draft formal language for this and make it a normative part of the standard, so it settled for making it a note.
- And what is it about the members of unions? I really don't get a grasp of the meaning of this. Can anyone give me a simple example?
Consider this union:
union foo
{
void *v;
char *c;
float *f;
} u;
When we write into one union member, as with u.v = &a;
, and read from another union member, as with char *p = u.c;
, the bytes in the union are reinterpreted in the new type (C 2018 6.5.2.3 3 and note 99). Since void *
and char *
have the same representation, this reinterpretation must produce the same value. Thus, we are guaranteed that:
char a;
u.v = &a;
printf("%d\n", u.c == &a);
prints “1”. On the other hand, we are not guaranteed that for this code:
float f;
u.v = &f;
printf("%d\n", u.f == &f);
In this code, when &f
is converted to void *
, a void *
might have a different representation from a float *
, so the bytes representing &f
may be different from the bytes representing (void *) &f
. The latter are the bytes stored in u.v
. When those bytes are read as u.f
and reinterpreted as a float *
, they might represent a different value, so the comparison might not evaluate as true.
Footnote
1 The question cites “6.2.5.27,” but the quoted passage is found in clause 6.2.5, paragraph 28, of the official 2018 C standard. The note cited as note 39 is found as note 49.
void*
and back without a cast. That is the basis for the interchangeability.No implicit conversion takes place, avoid*
pointer is simply a pointer without a specific type. And, since type controls pointer arithmetic, you can't do pointer arithmetic onvoid*
pointers. Just like you cannot dereference avoid*
pointer because the type information is missing -- it would be an incomplete type. So you have to assign or cast a void pointer before dreferencing. – Bounteousvoid*
andchar*
having the same representation.void*
doesn't have to have the same representation or alignment as other pointer types. – Teemingchar*
exception) I haven't done a critical interpretation of the standard section, just more a practical discussion of how that section doesn't hold any hidden gotchas. – Bounteousvoid *foo(void *p)
in one translation unit and declarechar *foo(char *p)
in another, and call it using the latter, and it would work because the types are interchangeable. But it is undefined according to the normative text of the C standard, aside from this passage in C 2018 6.2.5 28 about the same representation. – Foghorn