Question Details

No question body available.

Tags

c pointers casting strict-aliasing

Answers (2)

March 23, 2026 Score: 2 Rep: 234,541 Quality: Medium Completeness: 100%

What does exempting unsigned char type specifically from this rule achieve in this case?

For exactly your use case, i.e. accessing the underlying representation of an object.

Section 6.5p7 of the C23 draft standard spells out the alias rules:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:99)

  • a type compatible with the effective type of the object,
  • a qualified version of a type compatible with the effective type of the object,
  • a type that is the signed or unsigned type corresponding to the effective type of the object,
  • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
  • a character type.

99) The intent of this list is to specify those circumstances in which an object can or cannot be aliased.

The bolded parts above are what allow using a pointer to a character type to access a pointer to other types.

Section 6.3.2.3p7 regarding pointer conversions spells out what this is used for:

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.

What you're doing is exactly what the above explains: using an unsigned char to access the object representation of a double. Your environment is presumably using IEEE754 double precision floating point representation in little endian format for type double, and you're taking advantage of that information to extract the sign bit from the double value in question.

March 23, 2026 Score: 2 Rep: 235,569 Quality: Medium Completeness: 60%

Ideally, the compiler would always know when different lvalues referred to different objects. Whenever different lvalues refer to different objects, the compiler can optimize freely—it can load an lvalue far in advance of when it is needed (which allows time for it to be fetch from memory if it is not in cache at the moment), and it can keep it in registers (which can reduce the number of loads and stores a program needs) while other lvalues are loaded and stored.

But we do not have this ideal. The compiler often does not know whether two pointers point at the same object or not. The more often we can let a compiler know different pointers point to different objects, the more opportunity there is for optimization.

One rule we could make is that any two lvalues with incompatible1 types point to different objects. Then a compiler would know that an *p of type float is a different object from an extern int x.

However, this rule is too much. Sometimes programs do use different (and incompatible) types to refer to the same object. One is in the situation where you wanted to access a double using a long type. Here is another: Reading and writing data from files. We may want to write binary data to a file for storage, because that is efficient, so we want to write all of our objects, arrays of float, arrays of structures, and more. But we need a general interface for files, and that is generally done using buffers of character type.

So, since we cannot make a complete rule that any two values with incompatible types point to different objects, we open as small a hole in the rule as possible: Character types can alias other types, and only character types can do that.2

And that is enough. We do not need to grant any other types this special status because any aliasing can be done using character types. If you want to alias a double using a long (in implementations where they are the same size), you can easily copy a double d into a long l using memcpy(&l, &d, sizeof l), do any work you want on l, and then copy the bytes back with memcpy(&d, &l, sizeof d). (memcpy is defined to work as if by copying bytes.) So we do not need to make the hole in the rule any larger.

As long as we are making a hole in the rule, then we do not need to make it any larger than for character types. Since each character type is a single byte, an array of them can cover any object of any size. If we used a larger type instead, this would not be true.

It is unfortunate that this rule means the compiler cannot assume an lvalue with character type does not alias any other object. In retrospect, that could have been avoided by designing a special data type, say data, that was equivalent to an unsigned char except it could alias other objects. Then programs could use ordinary character types, and the compiler would know they did not alias other objects, and programs would only use the data type when the were deliberately aliasing objects.

Footnotes

1 In the C standard, what it means for types to be compatible is essentially that they could be the same type if they are completed. For example, an array of an unknown number of int is compatible with an array of 10 int.

2 It is not quite only character types. There are some other exceptions. For historic reasons, corresponding signed and unsigned types can alias each other. And a structure can alias the types of its members because of course writing a new value to a structure has to change its members too. But character types are the ones granted the special status for aliasing.