cmemory-alignmentgcc-warningtypecasting-operator

Why does -Wcast-align not warn about cast from char* to int* on x86?


I understand that gcc has an option -Wcast-align which warns whenever a pointer is cast such that the required alignment of the target is increased.

Here's my program:

char data[10];
int ptr = *((int *)data);

On my machine, the alignment requirement of data is 1 whereas it's 8 for ptr.

Why don't I get a warning?

Could it be because I'm compiling it for x86?


Solution

  • Update 2020-05-10: As of GCC 8, the compiler supports the option -Wcast-align=strict, which will warn even on targets that are normally permissive of unaligned memory accesses. It is probably a good idea to have it enabled: the compiler is free to optimise code with the assumption that the pointers are aligned, even on targets that would otherwise not care.


    The warning will never be emitted when compiling for Linux i386 or x86-64, when using the standard ABIs for these systems. Let me explain you why that is so.

    First, let's see what gcc's documentation has to say about -Wcast-align :

    Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if a char * is cast to an int * on machines where integers can only be accessed at two- or four-byte boundaries.

    The Intel architecture does not require alignment of integers when using general-purpose instructions. Quoting from Intel's Basic Architecture manual, chapter 4.1.1 Alignment of Words, Doublewords, Quadwords, and Double Quadwords :

    Words, doublewords, and quadwords do not need to be aligned in memory on natural boundaries. The natural boundaries for words, double words, and quadwords are even-numbered addresses, addresses evenly divisible by four, and addresses evenly divisible by eight, respectively. However, to improve the performance of programs, data structures (especially stacks) should be aligned on natural boundaries whenever possible.

    Alignment, therefore, is not strictly necessary, though highly recommended. There is, however, one exception to that rule, which you may have had in mind. Bit 18 of the EFLAGS register is known as the "Alignment Check" bit, and bit 18 of the CR0 register is known as the "Alignment Mask" flag. When they are both set to 1, any memory accesses to data which is not aligned at its "natural boundary" (so, 2 bytes for words, 4 bytes for doublewords, and so on) results in #AC, the Alignment Check Exception. If you want to find out more about this, check out the Intel System Programming Guide.

    However, neither the System V ABI for i386, nor the System V ABI for x86-64 specify that the Alignment Flag in EFLAGS is set. In fact, the i386 ABI notes the following on page 29, chapter 3-3 Machine Interface :

    The Intel386 architecture does not require all data access to be properly aligned. (...) Consequently, arbitrary data accesses, such as pointers dereference or reference arguments, might or might not be properly aligned. Accessing misaligned data will be slower than accessing properly aligned data, but otherwise there is no difference.

    Although it also recommends that :

    Compilers should allocate independent data objects with the proper alignment.

    GCC always knows the ABI of the platform for which it compiles code, and - in case of x86/64 - is aware of the fact that unaligned data access is allowed. This is why code like this will compile without a warning about alignment (let's forget about strict aliasing rules in the following examples) :

    int main(void)
    {
        char foo[] = "foobar";
        int bar = *(int*)(foo + 1);
        return 0;
    }
    

    If you try to compile this code with the gcc toolchain for ARM, you will get the warning :

    daniel@Jurij:/tmp$ arm-linux-gnueabi-gcc -Wcast-align align.c 
    align.c: In function 'main':
    align.c:4:13: warning: cast increases required alignment of target type [-Wcast-align]
      int bar = *(int*)(foo + 1);
    

    This is because unaligned access is generally best avoided in ARM. I'm not an ARM expert, so I really can't say anything more than that.

    Also, please note that most of what I wrote does not apply to SSE/AVX.