To start with, I don't think you can talk about the alignment requirement of an incomplete type, because alignment requirements are only defined for complete types:
Complete object types have alignment requirements which place restrictions on the addresses at which objects of that type may be allocated. (§6.2.8/1; all standard citations are taken from n1570.pdf, which is effectively C11)
But in a valid program, a pointer must point to an object of complete type (or NULL). So even if the referenced type of a pointer is incomplete in some translation unit, the object referred to by that pointer (if there is one) must have a complete type and thus an alignment requirement. However, in the translation unit in which the referred type is incomplete, there is no way to know what that alignment requirement is, neither for the compiler nor for the person writing the code.
So the only way a pointer to an incomplete type can be legitimately assigned a non-NULL value is by means of a copy of a pointer to the same type from somewhere where the type is complete (perhaps in another translation unit). This is not a conversion, since the two types are compatible, and it is guaranteed to work. Similarly, one of the few legitimate uses for a value of a pointer to an incomplete type is to pass it to a pointer to the same type in a context where the type is complete. (Other uses include comparing it for equality with another pointer value or converting it to an integer and printing it out, but these don't involve converting to a different pointer type.)
So, in summary, pointers to incomplete types are useful and usable precisely in the expected use case -- where the referenced type is opaque -- but cannot be portably converted to pointers of any other type except for possibly qualified char*
and void*
.
The standard does not guarantee the possibility of converting a pointer to a pointer to a type whose alignment might be more stringent. If the alignment of the referred type is unknown, the only pointers whose target alignment cannot be more stringent are char*
and void*
. So any conversion of a pointer to an incomplete type to a type other than char*
or void*
must be regarded as unportable.
Really, the fact that the referred type is incomplete is not hugely relevant. The standard does not specify the alignment of a composite type. Such an alignment must be sufficient to allow the compiler to correctly align members, but it could be arbitrarily large. In other words, there is no guarantee that the types:
typedef char oneChar;
struct oneChar_s { char x; };
union oneChar_u { char x; };
have the same alignment. It is quite possible for the two composite types to have larger alignments than 1. So there is no guarantee that it is possible to convert a oneChar*
to a oneChar_s*
(unless, of course, the oneChar*
were the result of a previous conversion in the opposite direction), and a portable program would not try. In this sense, it makes no difference whether the definition of struct oneChar_s
is visible or not.
Not coincidentally, the standard does not guarantee that all object pointers have the same size. The underlying theory is that on some architectures, ordinary pointers are not sufficiently precise to refer to single bytes, but there is the possibility of augmenting the pointer with the addition of, for example, a bit offset. Indeed, it might be the case that there are other small objects which can be packed into words, which also require augmented pointer representations but with less precision than a bit offset.
In such an architecture, it is not possible to take advantage of different pointer precisions for small composite objects, because the standard insists that there be at most two representation of pointers to composites, one for struct
s and one for union
s:
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. (§6.2.5/27) [Note 2]
In particular, this means that pointers to objects of a given type have the same representation, regardless of whether the type is complete.
Difference in pointer representations is not the only reason that a conversion to a more constrained alignment might fail. For example, an implementation might insert code to verify alignment after a conversion (perhaps in response to a sanitizing compiler option). In the case of an incomplete type, the compiler would not be able to insert static code to do the check (although some kind of runtime check might be possible), but the fact that the compiler might omit the validation code would not alter the undefinedness of the result.
For what its worth, the standard citation in the OP was for C99; in C11, it has been modified slightly (emphasis added to indicate changed wording):
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced 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. (§6.3.2.3/7)
In my opinion, this change is purely editorial. It derives from the decision to change the definition of "object type" in §6.2.5/1. In C99, there were three kinds of type: object types, function types, and incomplete types. In C11, there are only two kinds -- object types and function types -- with the comment that "At various points within a translation unit an object type may be incomplete… or complete…", which is a more accurate description.
Notes
As a completely hypothetical example, consider a machine conceptually similar to the PDP-6/10 architecture. This is a word-addressed machine with a large word size; a word is large enough to contain two addresses (a fact which a hypothetical LISP implementation could take advantage of in order to store a cons node consisting of car
and cdr
fields into a single word). Because it is desired to represent vectors of small objects efficiently, the machine also has instructions which can extract or overwrite a bitfield within a word, where the bitfield pointer consists of a word pointer accompanied by an offset and length information. (Hence, a word can contain only one bitfield pointer.) (The hardward has an instruction which can increment bitfield pointers by adding the length to the offset and moving to bitfield starting at 0 of the next word if necessary.)
So there could be three different pointer types:
- a fullword character pointer consisting of an address and a bitfield offset/length.
- an ordinary halfword pointer to any word-aligned object type.
- an augmented halfword-plus-one-bit pointer to a pointer to a word aligned object, consisting of an address and an indication of whether the address is in the first or second half of the word. (This representation also probably requires a fullword, but the encoding is simpler. But there might also be some other place for the extra bit. It's a hypothetical example, remember.)
In this hypothetical architecture, the conversion rules become quite complicated. For example, you can convert an char**
to an int*
because the alignment of int
is the same as the alignment of char*
. But you cannot convert an int**
to an int*
because the alignment of int
is greater than the alignment of int*
.
Rather than memorizing these complex rules, a programmer would probably choose to simply forbear from performing pointer conversions other than those guaranteed to be portable, which is round-tripping through char*
or void*
.
It would be possible for pointers to all composites to use the larger, more precise pointer type, even if unnecessarily. It seems to me much more likely that an implementation would simply choose to impose a minimum alignment on struct
s, if not all composite objects. The wording of the standard would allow an implementation to use a minimum alignment for struct
s and an augmented pointer representation for all union
s.