5

I've used C pointers for some time now and everything has always worked as expected. However now I've run into the ISO standard that says "If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined".

This brings me to ask myself:

  1. What does it mean?

  2. How can we safely cast pointers to other types?

I've found this C code example that is said to be incorrect because it has to do with more strictly aligned pointer types:

int main() {
    
    char c = 'x';
    int *ip = (int *)&c;
    char *cp = (char *)ip;

    return 0;
}
  1. What is meant by more strictly aligned pointer types?
chqrlie
  • 131,814
  • 10
  • 121
  • 189
alessio solari
  • 313
  • 1
  • 6
  • `int *ip = (int *)&c;` may access memory that is not defined by `char c`. – Robert Harvey Jul 25 '23 at 15:37
  • Rovert Harvay, I know that. The problem I'm talking about is another one. Please read carefully the questions – alessio solari Jul 25 '23 at 15:39
  • 1
    Depending on the platform, an `int` may require 4 byte alignment, while a `char` only requires single-byte alignment. The problem seems clear enough. – Robert Harvey Jul 25 '23 at 15:39
  • Robert Harvay, ok then what are the rules to keep in mind to be 100% sure that our cast is correct when we casting a pointer to a different type ? – alessio solari Jul 25 '23 at 15:44
  • 1
    @MooingDuck That's C++, the question is about C. – Barmar Jul 25 '23 at 16:13
  • You can usually cast from a pointer that requires stricter alignment to a pointer that requires less strict requirement, i.e. from a bigger to a smaller type For example, if you want to read binary `int` values from a byte stream, define an `int` array and cast it to a `char *`, don't define `char` array and cast it to an `int *`. – Bodo Jul 25 '23 at 16:19
  • @alessiosolari This question has some code you can experiment with, assuming you're running on an x86 system: [**Prohibit unaligned memory accesses on x86/x86_64**](https://stackoverflow.com/questions/11837550/prohibit-unaligned-memory-accesses-on-x86-x86-64) – Andrew Henle Jul 25 '23 at 17:03

3 Answers3

2
  1. What is meant by more strictly aligned pointer types ?

C 2018 6.2.8 5 says:

Alignments have an order from weaker to stronger or stricter alignments. Stricter alignments have larger alignment values. An address that satisfies an alignment requirement also satisfies any weaker valid alignment requirement.

In case you do not have a specification for “alignment,” C 2018 6.2.8 1 says:

Complete object types have alignment requirements which place restrictions on the addresses at which objects of that type may be allocated. An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated…

In effect, this means an object with alignment requirement A must start at an address that is a multiple of A.

  1. What does it mean?

Consider some pointer with value P. That is, P is the address to which the pointer points. For purposes of this answer, we take P to be a fully resolved address in the memory space. (Actual pointers may be represented with references to various base addresses or segments, as well as offsets from those bases or from the starts of segments.) When the pointer is convert to a pointer to a different object type, the new object type has some alignment requirement A. If P is a multiple of A, then P is correctly aligned for the object type, and the conversion produces some new valid pointer. (It is not necessarily a pointer that can be used to access an object of the new type, due to other rules in C.) If P is not a multiple of A, then the C standard does not define the behavior of the program. The conversion might not produce a valid pointer, it might produce a pointer adjusted to be correctly aligned, the program might abort, or the program might behave in ways you do not expect.

  1. How can we safely cast pointers to other types ?

Only convert a pointer to a pointer to a new object type if you know the source pointer is correctly aligned for the destination type.

Given char c;, converting &c to int * is generally not safe because a char may be given any starting address, but int typically has an alignment requirement of four bytes in modern C implementations and at least two even in old C implementations. So there is a hazard that &c is not suitably aligned for int.

The alignment requirements for types vary between C implementations. If a pointer points to an object with an alignment type as strict or stricter than the new type, then you know it can be converted safely. Otherwise, the C implementation may provide means for testing the alignment. Commonly, converting to a uintptr_t provides a representation of the address that can be tested to see if it is a multiple of the required alignment. The alignment requirement of a type can be obtained by evaluating _Alignof(type-name).

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • It would be helpful if you explained how the example code specifically violates the alignment rules -- `char*` has alignment 1, `int*` typically has alignment 4 or 8, so you can cast from `int*` to `char*`, but not vice versa. – Barmar Jul 25 '23 at 16:16
  • Eric Postipischil, I've come to the conclusion that the conversion that is always 100% safe is from a pointer to an object of type T to void*. We are guaranteed that when we we go back to T we're not gonna have bad surprises. Do you agree with that ? – alessio solari Jul 25 '23 at 16:29
  • 1
    @alessiosolari: Yes, converting a pointer to an object type to `void *` and back to the same object type is safe. The alignment requirement of `void` is the minimum, 1 byte, and, for the conversion back, the pointer is known to satisfy the alignment requirement for the destination type because it originated with that type. – Eric Postpischil Jul 25 '23 at 16:32
2

Erik has thoroughly covered question (1) "What does it mean?" and question (3) "What is meant by more strictly aligned pointer types?".

As for ...

  1. How can we safely cast pointers to other types ?

Erik basically said that it is safe to convert from any type T * to any type U * if alignof(U) <= alignof(T), which is absolutely true, and the most general and comprehensive answer possible. However, you seem to be looking for some practical rules, and explicitly checking alignment requirements is not really useful at runtime, and produces implementation-specific results if done during development. You may find these rules more practical:

It is safe to

  • Cast from any object-pointer type to void *
  • Cast from any object-pointer type to char *, signed char * or unsigned char *
  • Cast among pointers to types differing only in signedness and / or type qualifiers.
  • Cast from a pointer to a structure type to a pointer to the type of the structure's first member
  • Cast from a pointer to a union type to a pointer to the type of any member of the union
  • Cast from a pointer to an array type to a pointer to the array's element type, and vice versa
  • Cast directly from a type T * to a type U * if one can safely convert from T * to U * via one or more safe intermediate conversions.
  • Cast between any two function-pointer types
  • given a pointer value p obtained via a series of pointer conversions that do not any of which invoke undefined behavior, whether or not their safety was provable in advance, convert p back to the original type or to any of the intermediate types.

Note well that the safety of a pointer conversion is only about the conversion itself. It is not necessarily safe to dereference the pointer resulting from a safe (in the sense we're discussing) conversion, or, for function pointers, to call the pointed-to function via the converted pointer.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

How can we safely cast pointers to other types ?

In addition to aspects covered in other answers ...

Be wary of casting between object pointrers like void *, int *, unsigned char *, ... and function pointers line int (*p)().

A function pointers may be wider than all object pointers (or rarely less). Thus code like void *p = main, may lose important information.

In general, avoid casting function pointers to object pointers until you are clear on the limitations - even then, consider other approaches.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256