0

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?

Amine
  • 1,396
  • 4
  • 15
  • 32
  • 4
    You can, but the behavior will be implementation defined as the types are considered to be incompatible. And theoretically the pointers might have different sizes and point to different memories. The only pointer type which is compatible with any other is `void *`. – Eugene Sh. Sep 19 '18 at 13:56
  • @EugeneSh. can it be done with or without casting? – Amine Sep 19 '18 at 13:59
  • 2
    This is something you can try yourself. – Eugene Sh. Sep 19 '18 at 14:00
  • A char pointer can safely alias another object, i.e. if you have a int pointer it ok to assign it to a char pointer. See https://stackoverflow.com/a/35496066/4386427 – Support Ukraine Sep 19 '18 at 14:09

2 Answers2

3

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:

  • Two types are compatible if they are the same.
  • Additional rules are in 6.7.2, 6.7.3, and 6.7.6.
  • Two structure, union, or enumerated types declared in separate translation units are compatible if they are, in essence, declared identically. (Interestingly, two such types declared in the same translation unit are not compatible.)

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 1) Good this answer brings up difference between pointer to an object type and function types. 2) I'd highlight the important last paragraph or put its conclusion also at the beginning. – chux - Reinstate Monica Sep 19 '18 at 15:53
2

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.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • This is overly simplistic. Assignments between `T1 *` and `T2 *` are allowed if `T1` and `T2` are compatible, even if they are different. – Eric Postpischil Sep 19 '18 at 14:57
  • "unless at least one of T1 and T2 is void" has a corner exception. pointer to a function. A pointer to a function and `void*` are not specified to well convert. Even in 2018, various machines have different size function pointers and `void*`. Your answer does well apply to `void*` and _object_ pointers. – chux - Reinstate Monica Sep 19 '18 at 15:50
  • @chux Another good point. Answer amended again. I shouldn't try to answer in a hurry. (And speaking of function pointers, let's not get into the magical world of split I&D address spaces...) – Steve Summit Sep 19 '18 at 16:03
  • Note about the "all popular .. today" in "especially on machines (such as all popular ones today) with "flat" address spaces. The number of embedded processors now is over a [billion/year](https://en.wikipedia.org/wiki/Microchip_Technology#Microcontrollers). A significant percentage use non-flat compatible object/function pointers. – chux - Reinstate Monica Sep 19 '18 at 16:16