Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does casting a T pointer to a T' pointer and back yield the original pointer if T' is an incomplete type?

Consider the following snippet which is adapted from the Tor source code:

/* This can be a malloc wrapper with minimal initialization. */
other_t *make_other(void);

/* This struct is never defined. */
struct undef;
typedef struct undef undef_t;

undef_t *make_undef(void)
{
    other_t *other = make_other();
    return (undef_t*)other;
}

Assume that all undef_t pointers in the program are casted other_t pointers and assume further that all procedures which take undef_t* cast them to other_t* before use.

According to section 6.3.2.3 of the C99 standard, the cast within the return statement invokes undefined behavior if other is not correctly aligned as a pointer to undef_t, but if it is, casting the result of make_undef back to other_t* yields the original pointer as returned by make_other. However, undef_t is an undefined type, and I cannot find any alignment rules regarding these. Do these conversions still work like they would if undef_t was defined and had the right alignment?

like image 921
J. Doe Avatar asked Jul 20 '16 09:07

J. Doe


People also ask

What happens when you cast a pointer?

A pointer is an arrow that points to an address in memory, with a label indicating the type of the value. The address indicates where to look and the type indicates what to take. Casting the pointer changes the label on the arrow but not where the arrow points.

What happens when you cast a pointer to an int?

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

Can we type cast pointers?

We saw that pointer values may be assigned to pointers of same type. However, pointers may be type cast from one type to another type. In the following code lines, A is an int type variable, D is variable of type double, and ch is a variable of type char.


1 Answers

I don't think it makes any difference whether or not a type is incomplete.

Per C11 6.2.5.22, there are only three kinds of incomplete type that can be instantiated:

  1. An array type of unknown size
  2. A structure type of unknown content
  3. A union type of unknown content

Further, per C11 6.2.5.28:

All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other.

So the representation and alignment requirements for a pointer to a structure or a union are known, whether the type is complete or not, since you still know it's a pointer to a structure or union of some kind.

For arrays, since we know from C11 6.3.2.1.3 that:

an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object

then we can conclude that a pointer to, say, int, and a pointer to an array of int must have the same alignment requirements, because you can use either to refer to the same object. In other words, all pointers to arrays of int have the same alignment requirements, whether the size is known or not.

So if you know you have a pointer to a structure, or if you know you have a pointer to a union, or you know you have a pointer to an array of a specified type, then you know what the alignment requirements are. The fact that the type is incomplete doesn't matter. Indeed, if this were not true, incomplete types would not be useful, because you'd never be able to reliably use pointers to them.

In your case, both undef_t and other_t are typedefd somewhere in your translation unit, so you know what kind of incomplete type you have, and you therefore know the alignment requirements for it, so there is no ambiguity created by the fact the type is incomplete. Of course, if you as the programmer use them without going to the trouble of finding this out for yourself, then you can still run into problems, if for example other_t is a pointer to a union and undef_t is a pointer to a structure. But this is just a normal problem of converting between pointers which may have different alignment requirements - nothing about this problem is made more difficult by one of more of the pointed-to types being incomplete.

EDIT - some further elucidation, based on comments:

A "pointer to incomplete type" can only ever point at an object of a complete type, because incomplete types obviously by definition cannot be instantiated (for the sake of brevity, I'm setting aside the possibility of malloc()ing an arbitrary amount of memory and pointing a pointer of incomplete type at it).

Pointers to incomplete types exist for the sake of being able to have and pass around pointers to aggregate and compound types without needing to have the type definition available. In other words, when you don't have enough information to be able to instantiate a particular type, but you do have enough information to be able to point to one. Opaque types work on this basis, where all the actual work is done in functions where the type definition is available, and the code that uses those types only needs to be able to store the address of the object so it can be passed to and from these functions.

In terms of alignment, the representation of a pointer can change depending on the alignment requirements of the object it points to. For instance, as pointed out in the comments, if all ints must be stored on 8-byte boundaries, then a pointer representation could potentially not store the 3 least significant bits, because they'll always be zero. But if you then attempt to convert a char pointer to an int pointer and back, you can lose information, because a char pointer must be able to point to individual bytes, and some of that information would be lost in this hypothetical conversion to int *.

Similarly, it might be case that some small structures could be aligned on, say, 4 byte boundaries, and larger ones on 32 byte boundaries. But the alignment requirements for pointers to structures must all be the same, precisely because of these pointers to incomplete types. At the place where you use a pointer to an incomplete structure type, you don't have the type definition available (if you did, it would be a complete type), and so you don't know whether you can ignore the least significant 5 bits, or only the least significant 3 bits, for example. So the alignment requirements for a pointer to an incomplete structure type have to be sufficiently permissive that they can correctly hold the location of any conceivable structure type. To make passing these around easier, we can see from the above quote that C requires all pointers to structure types to have the same alignment requirements. Some structures might still actually be aligned on 32 byte boundaries, for instance, but a pointer to that structure cannot be permitted to take full advantage of this, and it must be capable of holding the location of any structure type.

But, if all structure types were aligned on no smaller than 4 byte boundaries, for instance, then would be perfectly fine for pointers to structures, including pointers to incomplete structure types, to ignore the least significant 3 bits, because you can do that while still being sure that you can represent the location of any structure safely. Indeed the fact that C requires all pointers to structure types to have the same alignment requirements, and requires all pointers to union types to have the same alignment requirements, but does not require that the alignment requirements for pointers to structure types be the same as alignment requirements for pointers to union types, indicates that it would be possible for, say, pointers to structures to rely on a minimum 4 byte boundary, but for pointers to unions to rely on a minimum 8 byte boundary. In this case, you couldn't safely convert a pointer to an incomplete structure type to a pointer to an incomplete union type and back again.

This is why it's not correct to say, as another answer currently does, that "there are no alignment requirements for an incomplete type". But it does mean that incomplete types are not problematic for the original question, because as already explained, when you have an incomplete type, you at least know whether its a structure, or a union, or an array, and that's all you need to have complete knowledge of the alignment requirements.

like image 103
Crowman Avatar answered Sep 18 '22 00:09

Crowman