I’ve recently been perusing the C’11 standard final draft, mostly hoping to find some resolutions to the various inconsistencies and problems I’ve noted previously with the C99 standard (with no real success). In particular I read section 7.22.3 (C11; 7.20.3 in C99), which discusses the malloc family of functions:
The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object or an array of such objects in the space allocated …
A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function …
A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.
int a = 5; // 1 void *va = &a; // 2 float *fa = va; // 3 void *vb = fa; // 4 float *fb = vb; // 5 int *pa = vb; // 6
Now answer the following questions, assuming that the alignment requirements for all involved types are met (except when answering the first question). Write down your answers:
1. Is it possible for line (3) to produce undefined behavior, if the alignment requirements for ‘float’ are more stringent than that for ‘int’? (assume that line 3 does not produce undefined behavior for the following questions).
2. Would (fa == va) necessarily be true immediately after (3)?
3. Would (vb == va) necessarily be true immediately after (4)?
4. Would (fb == fa) necessarily be true immediately after (5)?
5. Would (pa == &a) necessarily be true immediately after (6)?
Here are my own answers, with discussion:
1. No, because 18.104.22.168p1 allows conversion from a void pointer to any type of pointer. The conversion is not necessarily allowed in both directions, however. 22.214.171.124p7 does not come into play because a pointer to void is not a pointer to an object type.
Yes, I think, because 126.96.36.199p7 comes into play. This does open the question of what the purpose of 188.8.131.52p1 is, however, since it appears to be completely encompassed by 184.108.40.206p7. (One could perhaps be forgiven for thinking that 220.127.116.11p1 is trying to remove the alignment requirements in the case of conversion from the “void *” type).
2. No. Nowhere does it state that these pointers must compare equal.
3. No. As per costeau’s comment below, the transition from void-pointer-to-object-pointer-to-void-pointer is not guaranteed to yield a pointer equal to the original.
Yes. We converted a “void *” (va) to a “float *” and back again, so by 18.104.22.168p1 (and p7) the result must compare equal with the original. This shouldn’t be surprising.
4. Yes, by 22.214.171.124.
p7 p1. Again, this shouldn’t be surprising.
This is the really disturbing result. Although va and vb must compare equal (and therefore point at the same object), no further requirements are placed on the similarity of their behavior. (actually there is no requirement that va and vb compare equal! I have really goofed this up). Thus, converting them both to “int *” might yield different pointer values which do not compare equal (and which do not point at the same object!). Putting it another way, although va and vb must compare equal (wrong…), they need not be the same value!
Edit: A much better example, because I got that so wrong before:
int a = 5;
void *ap = &a;
void *bp = &a;
Note that it is not necessarily true that ap == bp, but it is true that (int *)ap == (int *)bp.
More generally, take this example:
char *a = malloc(sizeof(int));
void *r = a;
float *f = r;
Note that there is no guarantee now that f addresses the same object as does a (and indeed, there’s no guarantee that r holds a pointer equal to the one that was returned by the call to malloc!). So, if we were able to obtain a pointer to a chunk of memory (i.e. an object) there’s actually no way we could use it, unless it happened to be the right pointer type to begin with.
I really, really, do not like the answer for 5, but I can’t find a way to come to the opposite conclusion based on the standard as worded. Note that in general, conversion from “void *” to another type of pointer – in fact, conversion of any “T *” to another pointer type other than “char *” – is not very well defined. Interestingly, the only thing in the standard that lets us use the result of the malloc() function is the documentation for malloc() itself (as quoted earlier). But creating a custom, general-purpose memory allocation (malloc-like facility) is just not possible. You simply can’t return a usable “void *” value unless you derived it from a pointer to the desired type in the first place. Am I missing something?