Considering
T1 *p1;
T2 *p2;
Can we assign p1 to p2 or vice versa? If so, can it be done without the casting or we must use a cast?
Considering
T1 *p1;
T2 *p2;
Can we assign p1 to p2 or vice versa? If so, can it be done without the casting or we must use a cast?
First, let’s consider assignment without casting. C 2018 6.5.16.1 1 lists constraints for simple assignment, saying that one of them must hold. The first two are for arithmetic, structure, and union types. The last two deal involve null pointer constants or _Bool
. The middle two deal with assigning pointers to pointers:
the left operand has atomic, qualified, or unqualified pointer type, and … both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right
the left operand has atomic, qualified, or unqualified pointer type, and … one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right
The latter says we can assign void *
to any object pointer and vice-versa, as long as no qualifiers (const
, volatile
, restrict
, or __Atomic
) are removed.
The former says we can assign pointers to compatible types, as long as no qualifiers are removed. What are compatible types?
6.2.7 1 says:
6.7.2 4 says each enumerated type (an enum
type) is compatible with an implementation-defined choice of char
or a signed or unsigned integer type. So a pointer to some enum
can be assigned to a pointer to one char
or integer type (and vice-versa), but you cannot know which one without knowing something about the particular C implementation.
6.7.3 11 says qualified types must have the same qualifiers to be compatible. Thus an int
is not compatible with a const int
, and this prevents an int *
from being assigned to a const int *
.
6.7.6.1 2 says for two pointer types to be compatible, they must be identically qualified pointers to compatible types. This tells us, for example, that int *
is not compatible with char *
, and, therefore, by assignment constraints above, a char **
may not be assigned to an int **
.
6.7.6.2 6 says for two array types to be compatible, they must have compatible element types and, if they both have integer constant sizes, they must be the same. (This allows that an array with unknown size may be compatible with an array of known size. However, additional text says that if the arrays ultimately have different sizes, using them in a context that requires them to be compatible has undefined behavior. So an assignment of pointers to such arrays may satisfy its constraints and compile without error, but the resulting program may misbehave.)
6.7.6.3 15 presents somewhat complicated rules for the compatibility of function types. These rules are complicated because functions may be declared with or without parameter lists, with ellipses, and so on. I will omit complete discussion of these.
Those are the rules that tell you what pointer assignments may be made without casts.
6.5.4 discusses casts. Its constraints do not restrict which pointer types may be converted to which other pointer types. (They do prohibit other things involving pointers, such as converting a pointer type to a floating-point type.) So you can specify any pointer conversion you want in a cast, and, as long as the resulting type is compatible with the type to which it is being assigned, no assignment or cast constraint is violated. However, there is a still a question about whether the conversion is proper.
6.3.2.3 specifies rules for pointer conversions. Those dealing with conversion from pointers to pointers (excluding integers and null pointer constants) say:
Any pointer to an object type (not to function types) may be converted to a pointer to void and vice-versa. The result of converting an object pointer to a void pointer and back compares equal to the original.
A pointer may be converted to the same type with more qualifiers, and the result compares equal to the original.
A pointer to an object type may be converted to a pointer to a different object type if the resulting pointer is correctly aligned for its type (otherwise, the behavior is undefined). When converted back, the result compares equal to the original pointer. (Note that, while you are allowed to make this conversion, this rule does not say the resulting pointer may be used to access an object of the new type. There are other rules in C about that.)
A pointer to a function type may be converted to a pointer to another function type. When converted back, the result compares equal to the original pointer. (As with objects, you are allowed to make this conversion, but using the resulting pointer to call an incompatible function has undefined behavior.)
So, when casts are used, you may convert any object pointer type to any object pointer type and assign it as long as the alignment requirement is satisfied, and you may convert any function pointer type to any function pointer type and assign it.
When types T1
and T2
are different, assignments between T1 *p1
and T2 *p2
are formally disallowed, unless at least one of T1
and T2
is void
and the other is an object (not function) type.
In many cases incompatible assignments will work in practice, especially on machines (such as all popular ones today) with "flat" address spaces and where all pointer types share the same internal representation.
After a "mixed-mode" pointer assignment, however, problems may very well occur when the pointers are dereferenced, due to (1) alignment issues and (2) strict aliasing.
Since "mixed mode" pointer assignments are formally illegal and often a bad idea, most compilers will warn about them. Most compilers allow the warnings to be suppressed with an explicit cast. Most of the time, the cast serves only to suppress the warning; it does not introduce any actual conversion that wouldn't have been performed anyway. (That is, changing p1 = p2
to p1 = (T1 *)p2
is a lot like changing i = f
to i = (int)f
, where i
is an int and f
is a float.)
Addendum: I wrote, "When types T1
and T2
are different," but a more precise statement would be when they are incompatible. For example, types char
and unsigned char
are compatible, so assignment between pointers of those types is fine. See Eric Postpischil's longer answer for more details.