Qual é a regra de aliasing estrita?

Ao perguntar sobre o comportamento indefinido comum em C , almas mais esclarecidas do que eu me referi à regra de aliasing estrito.
Do que eles estão falando?

    Uma situação típica que você encontra problemas de aliasing estrito é quando sobrepondo uma estrutura (como um dispositivo / rede msg) em um buffer do tamanho da palavra do seu sistema (como um ponteiro para uint32_t s ou uint16_t s). Quando você sobrepõe uma estrutura a um buffer, ou a um buffer em uma estrutura desse tipo por meio de conversão de ponteiro, você pode facilmente violar regras estritas de alias.

    Então, nesse tipo de configuração, se eu quiser enviar uma mensagem para algo, eu teria que ter dois pointers incompatíveis apontando para o mesmo pedaço de memory. Eu poderia ingenuamente codificar algo assim:

     typedef struct Msg { unsigned int a; unsigned int b; } Msg; void SendWord(uint32_t); int main(void) { // Get a 32-bit buffer from the system uint32_t* buff = malloc(sizeof(Msg)); // Alias that buffer through message Msg* msg = (Msg*)(buff); // Send a bunch of messages for (int i =0; i < 10; ++i) { msg->a = i; msg->b = i+1; SendWord(buff[0]); SendWord(buff[1]); } } 

    A regra de aliasing restrita torna essa configuração ilegal: desreferenciar um ponteiro que aliases um object que não é de um tipo compatível ou um dos outros tipos permitidos pelo C 2011 6.5 parágrafo 7 1 é o comportamento indefinido. Infelizmente, você ainda pode codificar desta forma, talvez receber alguns avisos, compilar bem, apenas para ter um comportamento inesperado quando executar o código.

    (O GCC parece um pouco inconsistente em sua capacidade de fornecer avisos de aliasing, às vezes nos dando um aviso amigável e às vezes não.)

    Para ver por que esse comportamento é indefinido, temos que pensar sobre o que a regra de aliasing restrita compra o compilador. Basicamente, com esta regra, não é necessário pensar em inserir instruções para atualizar o conteúdo de buff cada execução do loop. Em vez disso, ao otimizar, com algumas hipóteses incomodamente insuficientes sobre o aliasing, ele pode omitir essas instruções, carregar buff[0] e buff[1 ] nos registradores da CPU uma vez antes da execução do loop e acelerar o corpo do loop. Antes que o aliasing estrito fosse introduzido, o compilador tinha que viver em um estado de paranoia que o conteúdo do buff pudesse mudar a qualquer momento de qualquer lugar por qualquer um. Então, para obter uma vantagem extra de desempenho, e assumindo que a maioria das pessoas não digita pointers, a regra de aliasing restrito foi introduzida.

    Tenha em mente que, se você acha que o exemplo é inventado, isso pode acontecer mesmo se você estiver passando um buffer para outra function que está fazendo o envio para você, se você tiver.

     void SendMessage(uint32_t* buff, size_t size32) { for (int i = 0; i < size32; ++i) { SendWord(buff[i]); } } 

    E reescreveu nosso loop anterior para aproveitar essa function conveniente

     for (int i = 0; i < 10; ++i) { msg->a = i; msg->b = i+1; SendMessage(buff, 2); } 

    O compilador pode ou não ser capaz ou inteligente o suficiente para tentar SendMessage in-line e pode ou não decidir carregar ou não carregar buff novamente. Se SendMessage fizer parte de outra API que é compilada separadamente, provavelmente tem instruções para carregar o conteúdo do buff. Então, novamente, talvez você esteja em C ++ e isso é apenas uma implementação de header de modelo que o compilador acha que pode inline. Ou talvez seja apenas algo que você escreveu em seu arquivo .c para sua própria conveniência. De qualquer forma, o comportamento indefinido ainda pode ocorrer. Mesmo quando sabemos o que está acontecendo, ainda é uma violação da regra, portanto, nenhum comportamento bem definido é garantido. Então, apenas envolvendo uma function que usa nosso buffer delimitado por palavra não ajuda necessariamente.

    Então, como eu consigo contornar isso?

    • Use uma união. A maioria dos compiladores suporta isso sem reclamar sobre o aliasing estrito. Isso é permitido em C99 e explicitamente permitido em C11.

       union { Msg msg; unsigned int asBuffer[sizeof(Msg)/sizeof(unsigned int)]; }; 
    • Você pode desativar o aliasing estrito em seu compilador ( f [no-] strict-aliasing no gcc))

    • Você pode usar char* para aliasing em vez da palavra do seu sistema. As regras permitem uma exceção para char* (incluindo signed char e unsigned char ). Sempre é assumido que char* codifica outros tipos. No entanto, isso não funcionará da outra maneira: não há suposição de que sua estrutura aliase um buffer de caracteres.

    Principiante, cuidado

    Este é apenas um campo minado em potencial quando se sobrepõem dois tipos um ao outro. Você também deve aprender sobre endianness , alinhamento de palavras e como lidar com problemas de alinhamento por meio de estruturas de embalagem corretamente.

    Nota de rodapé

    1 Os tipos que o C 2011 6.5 7 permite que um lvalue acesse são:

    • um tipo compatível com o tipo efetivo do object,
    • uma versão qualificada de um tipo compatível com o tipo efetivo do object,
    • um tipo que é o tipo assinado ou não assinado correspondente ao tipo efetivo do object,
    • um tipo que é o tipo assinado ou não assinado correspondente a uma versão qualificada do tipo efetivo do object,
    • um tipo de agregado ou união que inclua um dos tipos mencionados entre seus membros (incluindo, recursivamente, um membro de um sindicato subagregado ou contido), ou
    • um tipo de personagem.

    A melhor explicação que encontrei é de Mike Acton, Understanding Strict Aliasing . Está focado um pouco no desenvolvimento do PS3, mas é basicamente apenas o GCC.

    Do artigo:

    “O aliasing estrito é uma suposição, feita pelo compilador C (ou C ++), de que pointers de desreferenciação para objects de tipos diferentes nunca se referirão à mesma localização de memory (ou seja, alias entre si.)”

    Então, basicamente, se você tem um int* apontando para alguma memory contendo um int e então você aponta um float* para essa memory e usa-a como um float você quebra a regra. Se o seu código não respeitar isso, o otimizador do compilador provavelmente quebrará seu código.

    A exceção à regra é um char* , que é permitido apontar para qualquer tipo.

    Essa é a regra de aliasing estrita, encontrada na seção 3.10 do padrão C ++ 03 (outras respostas fornecem uma boa explicação, mas nenhuma forneceu a regra em si):

    Se um programa tentar acessar o valor armazenado de um object por meio de um lvalue diferente de um dos seguintes tipos, o comportamento é indefinido:

    • o tipo dynamic do object,
    • uma versão qualificada em cv do tipo dynamic do object,
    • um tipo que é o tipo assinado ou não assinado correspondente ao tipo dynamic do object,
    • um tipo que é o tipo assinado ou não assinado correspondente a uma versão qualificada em cv do tipo dynamic do object,
    • um tipo agregado ou sindical que inclua um dos tipos acima mencionados entre seus membros (incluindo, recursivamente, um membro de um sindicato subagregado ou contido),
    • um tipo que é um tipo de class base (possivelmente qualificado por cv) do tipo dynamic do object,
    • um tipo de unsigned char char ou unsigned char .

    Palavras em C ++ 11 e C ++ 14 (mudanças enfatizadas):

    Se um programa tentar acessar o valor armazenado de um object por meio de um valor diferente de um dos seguintes tipos, o comportamento é indefinido:

    • o tipo dynamic do object,
    • uma versão qualificada em cv do tipo dynamic do object,
    • um tipo similar (como definido em 4.4) ao tipo dynamic do object,
    • um tipo que é o tipo assinado ou não assinado correspondente ao tipo dynamic do object,
    • um tipo que é o tipo assinado ou não assinado correspondente a uma versão qualificada em cv do tipo dynamic do object,
    • um tipo de agregação ou união que inclua um dos tipos mencionados acima entre seus elementos ou membros de dados não estáticos (incluindo, recursivamente, um elemento ou membro de dados não estáticos de um subagregado ou união contida),
    • um tipo que é um tipo de class base (possivelmente qualificado por cv) do tipo dynamic do object,
    • um tipo de unsigned char char ou unsigned char .

    Duas alterações foram pequenas: glvalue em vez de lvalue e esclarecimento do caso agregado / união.

    A terceira alteração faz uma garantia mais forte (relaxa a regra de aliasing forte): o novo conceito de tipos semelhantes que agora são seguros para alias.


    Também a formulação em C (C99; ISO / IEC 9899: 1999 6.5 / 7; a mesma redação é usada na ISO / IEC 9899: 2011 §6.5 ¶7):

    Um object deve ter seu valor armazenado acessado apenas por uma expressão lvalue que tenha um dos seguintes tipos 73) ou 88) :

    • um tipo compatível com o tipo efetivo do object,
    • uma versão qualificada de um tipo compatível com o tipo efetivo do object,
    • um tipo que é o tipo assinado ou não assinado correspondente ao tipo efetivo do object,
    • um tipo que é o tipo assinado ou não assinado correspondente a uma versão qualificada do tipo efetivo do object,
    • um tipo de agregado ou união que inclua um dos tipos mencionados entre seus membros (incluindo, recursivamente, um membro de um sindicato subagregado ou contido), ou
    • um tipo de personagem.

    73) ou 88) A intenção desta lista é especificar as circunstâncias em que um object pode ou não ser aliased.

    O aliasing estrito não se refere apenas a pointers, ele também afeta referências, eu escrevi um artigo sobre isso para o wiki do desenvolvedor do boost e ele foi tão bem recebido que eu o transformei em uma página no meu site de consultoria. Ele explica completamente o que é, por que isso confunde tanto as pessoas e o que fazer sobre isso. White Paper de Aliasing Estrito . Em particular, explica por que os sindicatos são um comportamento arriscado para o C ++ e por que usar o memcpy é a única correção portável em C e C ++. Espero que isso seja útil.

    Como adendo ao que Doug T. já escreveu, aqui está um caso de teste simples que provavelmente o desencadeia com o gcc:

    check.c

     #include  void check(short *h,long *k) { *h=5; *k=6; if (*h == 5) printf("strict aliasing problem\n"); } int main(void) { long k[1]; check((short *)k,k); return 0; } 

    Compile com gcc -O2 -o check check.c . Normalmente (com a maioria das versões do gcc eu tentei) isso gera “problema de aliasing estrito”, porque o compilador assume que “h” não pode ser o mesmo endereço que “k” na function “check”. Por causa disso, o compilador otimiza o if (*h == 5) e sempre chama o printf.

    Para aqueles que estão interessados ​​aqui está o código assembler x64, produzido pelo gcc 4.6.3, rodando no Ubuntu 12.04.2 para x64:

     movw $5, (%rdi) movq $6, (%rsi) movl $.LC0, %edi jmp puts 

    Portanto, a condição if desapareceu completamente do código assembler.

    O tipo de punção por meio de lançamentos de ponteiro (em oposição a usar uma união) é um exemplo importante de quebra de aliasing estrito.

    De acordo com o raciocínio C89, os autores do Padrão não queriam que compiladores recebessem código como:

     int x; int test(double *p) { x=5; *p = 1.0; return x; } 

    deve ser solicitado a recarregar o valor de x entre a atribuição e a declaração de retorno, de modo a permitir a possibilidade de que p possa apontar para x , e a atribuição para *p possa, consequentemente, alterar o valor de x . A noção de que um compilador deve ter o direito de presumir que não haverá aliasing em situações como as descritas acima não é controverso.

    Infelizmente, os autores do C89 escreveram sua regra de uma forma que, se lida literalmente, faria até a seguinte function invocar o comportamento indefinido:

     void test(void) { struct S {int x;} s; sx = 1; } 

    porque ele usa um lvalue do tipo int para acessar um object do tipo struct S , e int não está entre os tipos que podem ser usados ​​acessando uma struct S Como seria absurdo tratar todo o uso de membros de estruturas e uniões do tipo não-caractere como Comportamento Indefinido, quase todos reconhecem que há pelo menos algumas circunstâncias em que um lvalue de um tipo pode ser usado para acessar um object de outro tipo. . Infelizmente, o Comitê de Padrões C não conseguiu definir quais são essas circunstâncias.

    Grande parte do problema é resultado do Relatório de Defeito # 028, que perguntou sobre o comportamento de um programa como:

     int test(int *ip, double *dp) { *ip = 1; *dp = 1.23; return *ip; } int test2(void) { union U { int i; double d; } u; return test(&u.i, &u.d); } 

    O Relatório de Defeito # 28 informa que o programa chama Comportamento Indefinido porque a ação de escrever um membro de união do tipo “duplo” e ler um do tipo “int” invoca o comportamento Definido pela Implementação. Tal raciocínio é absurdo, mas forma a base para as regras do Tipo Eficaz que desnecessariamente complicam a linguagem, sem fazer nada para resolver o problema original.

    A melhor maneira de resolver o problema original provavelmente seria tratar a nota de rodapé sobre o propósito da regra como se fosse normativa, e tornava a regra inaplicável, exceto em casos que realmente envolvem accesss conflitantes usando aliases. Dado algo como:

      void inc_int(int *p) { *p = 3; } int test(void) { int *p; struct S { int x; } s; sx = 1; p = &s.x; inc_int(p); return sx; } 

    Não há conflito dentro de inc_int porque todos os accesss ao armazenamento acessado através de *p são feitos com um lvalue do tipo int , e não há conflito no test porque p é visivelmente derivado de uma struct S , e na próxima vez que s é usado, todos accesss a esse armazenamento que já serão feitos através de p já terão acontecido.

    Se o código foi alterado ligeiramente …

      void inc_int(int *p) { *p = 3; } int test(void) { int *p; struct S { int x; } s; p = &s.x; sx = 1; // !!*!! *p += 1; return sx; } 

    Aqui, há um conflito de alias entre p e o access a sx na linha marcada porque nesse ponto da execução existe outra referência que será usada para acessar o mesmo armazenamento .

    Se o Relatório de Defeitos 028 dissesse que o exemplo original invocava o UB devido à sobreposição entre a criação e o uso dos dois pointers, isso tornaria as coisas muito mais claras sem ter que adicionar “Tipos Efetivos” ou outras complexidades desse tipo.

    Nota

    Este é extraído do meu “Qual é a regra de aliasing estrito e por que nos importamos?” escrever.

    O que é um aliasing estrito?

    Em C e C ++, o aliasing tem a ver com quais tipos de expressões nos é permitido acessar os valores armazenados. Em C e C ++, o padrão especifica quais tipos de expressão podem aliasar quais tipos. O compilador e o otimizador podem supor que seguimos estritamente as regras de alias, daí o termo regra de aliasing estrito . Se tentarmos acessar um valor usando um tipo não permitido, ele será classificado como comportamento indefinido ( UB ). Uma vez que tenhamos comportamento indefinido, todas as apostas estão desativadas, os resultados de nosso programa não são mais confiáveis.

    Infelizmente, com violações estritas do aliasing, muitas vezes obteremos os resultados esperados, deixando a possibilidade de que uma versão futura de um compilador com uma nova otimização quebrará o código que consideramos válido. Isso é indesejável e é um objective que vale a pena entender as regras estritas de aliasing e como evitar violá-las.

    Para entender mais sobre por que nos importamos, discutiremos questões que surgem ao violar regras de aliasing restritas, digitar punting, já que as técnicas comuns usadas no punning de tipos geralmente violam as regras de aliasing estritas e como digitar o trocadilho corretamente.

    Exemplos Preliminares

    Vejamos alguns exemplos, então podemos falar exatamente sobre o que os padrões dizem, examinar alguns exemplos adicionais e, em seguida, ver como evitar o aliasing estrito e detectar violações que perdemos. Aqui está um exemplo que não deveria ser surpreendente ( exemplo ao vivo ):

     int x = 10; int *ip = &x; std::cout < < *ip << "\n"; *ip = 12; std::cout << x << "\n"; 

    Temos um int * apontando para a memory ocupada por um int e este é um aliasing válido. O otimizador deve assumir que as atribuições através do ip podem atualizar o valor ocupado por x .

    O próximo exemplo mostra o aliasing que leva ao comportamento indefinido ( exemplo ao vivo ):

     int foo( float *f, int *i ) { *i = 1; *f = 0.f; return *i; } int main() { int x = 0; std::cout < < x << "\n"; // Expect 0 x = foo(reinterpret_cast(&x), &x); std::cout < < x << "\n"; // Expect 0? } 

    Na function foo tomamos um int * e um float * , neste exemplo chamamos foo e configuramos ambos os parâmetros para apontar para o mesmo local de memory que neste exemplo contém um int . Note que o reinterpret_cast está dizendo ao compilador para tratar a expressão como se ela tivesse o tipo especificado pelo parâmetro template. Neste caso, estamos dizendo para tratar a expressão & x como se tivesse o tipo float * . Podemos ingenuamente esperar que o resultado da segunda cout seja 0, mas com a otimização ativada usando -O2, tanto o gcc quanto o clang produzem o seguinte resultado:

     0 1 

    O que pode não ser esperado, mas é perfeitamente válido, já que invocamos um comportamento indefinido. Um float não pode validamente alias um object int . Portanto, o otimizador pode assumir que a constante 1 armazenada na desreferência será o valor de retorno, pois um armazenamento através de f não poderia afetar validamente um object int . Conectar o código no Compiler Explorer mostra exatamente o que está acontecendo ( exemplo ao vivo ):

     foo(float*, int*): # @foo(float*, int*) mov dword ptr [rsi], 1 mov dword ptr [rdi], 0 mov eax, 1 ret 

    O otimizador usando Análise de Alias ​​Baseada em Tipo (TBAA) assume que 1 será retornado e move diretamente o valor constante para o registrador eax, que carrega o valor de retorno. O TBAA usa as regras de idiomas sobre quais tipos podem ser alias para otimizar cargas e armazenamentos. Neste caso, o TBAA sabe que um float não pode alias e int e otimiza a carga de i .

    Agora, para o livro de regras

    O que exatamente o padrão diz que somos permitidos e que não podemos fazer? A linguagem padrão não é simples, então, para cada item, tentarei fornecer exemplos de código que demonstrem o significado.

    O que diz o padrão C11?

    A norma C11 diz o seguinte na seção 6.5 Expressões parágrafo 7 :

    Um object deve ter seu valor armazenado acessado apenas por uma expressão lvalue que tenha um dos seguintes tipos: 88) - um tipo compatível com o tipo efetivo do object,

     int x = 1; int *p = &x; printf("%d\n", *p); // *p gives us an lvalue expression of type int which is compatible with int 

    - uma versão qualificada de um tipo compatível com o tipo efetivo do object,

     int x = 1; const int *p = &x; printf("%d\n", *p); // *p gives us an lvalue expression of type const int which is compatible with int 

    - um tipo que é o tipo assinado ou não assinado correspondente ao tipo efetivo do object,

     int x = 1; unsigned int *p = (unsigned int*)&x; printf("%u\n", *p ); // *p gives us an lvalue expression of type unsigned int which corresponds to // the effective type of the object 

    O gcc / clang possui uma extensão e também permite atribuir int * não int * mesmo que não sejam compatíveis.

    - um tipo que é assinado ou não assinado correspondente a uma versão qualificada do tipo efetivo do object,

     int x = 1; const unsigned int *p = (const unsigned int*)&x; printf("%u\n", *p ); // *p gives us an lvalue expression of type const unsigned int which is a unsigned type // that corresponds with to a qualified verison of the effective type of the object 

    - um tipo agregado ou sindical que inclua um dos tipos mencionados entre seus membros (incluindo, recursivamente, um membro de um sindicato subagregado ou contido), ou

     struct foo { int x; }; void foobar( struct foo *fp, int *ip ); // struct foo is an aggregate that includes int among its members so it can // can alias with *ip foo f; foobar( &f, &f.x ); 

    - um tipo de personagem.

     int x = 65; char *p = (char *)&x; printf("%c\n", *p ); // *p gives us an lvalue expression of type char which is a character type. // The results are not portable due to endianness issues. 

    O que o Draft Standard C ++ 17 diz

    O padrão de rascunho do C ++ 17 na seção [basic.lval], parágrafo 11, diz:

    If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined: 63 (11.1) — the dynamic type of the object,

     void *p = malloc( sizeof(int) ); // We have allocated storage but not started the lifetime of an object int *ip = new (p) int{0}; // Placement new changes the dynamic type of the object to int std::cout < < *ip << "\n"; // *ip gives us a glvalue expression of type int which matches the dynamic type // of the allocated object 

    (11.2) — a cv-qualified version of the dynamic type of the object,

     int x = 1; const int *cip = &x; std::cout < < *cip << "\n"; // *cip gives us a glvalue expression of type const int which is a cv-qualified // version of the dynamic type of x 

    (11.3) — a type similar (as defined in 7.5) to the dynamic type of the object,

    (11.4) — a type that is the signed or unsigned type corresponding to the dynamic type of the object,

     // Both si and ui are signed or unsigned types corresponding to each others dynamic types // We can see from this godbolt(https://godbolt.org/g/KowGXB) the optimizer assumes aliasing. signed int foo( signed int &si, unsigned int &ui ) { si = 1; ui = 2; return si; } 

    (11.5) — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,

     signed int foo( const signed int &si1, int &si2); // Hard to show this one assumes aliasing 

    (11.6) — an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union),

     struct foo { int x; }; // Compiler Explorer example(https://godbolt.org/g/z2wJTC) shows aliasing assumption int foobar( foo &fp, int &ip ) { fp.x = 1; ip = 2; return fp.x; } foo f; foobar( f, fx ); 

    (11.7) — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,

     struct foo { int x ; }; struct bar : public foo {}; int foobar( foo &f, bar &b ) { fx = 1; bx = 2; return fx; } 

    (11.8) — a char, unsigned char, or std::byte type.

     int foo( std::byte &b, uint32_t &ui ) { b = static_cast('a'); ui = 0xFFFFFFFF; return std::to_integer( b ); // b gives us a glvalue expression of type std::byte which can alias // an object of type uint32_t } 

    Worth noting signed char is not included in the list above, this is a notable difference from C which says a character type .

    What is Type Punning

    We have gotten to this point and we may be wondering, why would we want to alias for? The answer typically is to type pun , often the methods used violate strict aliasing rules.

    Sometimes we want to circumvent the type system and interpret an object as a different type. This is called type punning , to reinterpret a segment of memory as another type. Type punning is useful for tasks that want access to the underlying representation of an object to view, transport or manipulate. Typical areas we find type punning being used are compilers, serialization, networking code, etc…

    Traditionally this has been accomplished by taking the address of the object, casting it to a pointer of the type we want to reinterpret it as and then accessing the value, or in other words by aliasing. Por exemplo:

     int x = 1 ; // In C float *fp = (float*)&x ; // Not a valid aliasing // In C++ float *fp = reinterpret_cast(&x) ; // Not a valid aliasing printf( “%f\n”, *fp ) ; 

    As we have seen earlier this is not a valid aliasing, so we are invoking undefined behavior. But traditionally compilers did not take advantage of strict aliasing rules and this type of code usually just worked, developers have unfortunately gotten used to doing things this way. A common alternate method for type punning is through unions, which is valid in C but undefined behavior in C++ ( see live example ):

     union u1 { int n; float f; } ; union u1 u; uf = 1.0f; printf( "%d\n”, un ); // UB in C++ n is not the active member 

    This is not valid in C++ and some consider the purpose of unions to be solely for implementing variant types and feel using unions for type punning is an abuse.

    How do we Type Pun correctly?

    The standard method for type punning in both C and C++ is memcpy . This may seem a little heavy handed but the optimizer should recognize the use of memcpy for type punning and optimize it away and generate a register to register move. For example if we know int64_t is the same size as double :

     static_assert( sizeof( double ) == sizeof( int64_t ) ); // C++17 does not require a message 

    we can use memcpy :

     void func1( double d ) { std::int64_t n; std::memcpy(&n, &d, sizeof d); //... 

    At a sufficient optimization level any decent modern compiler generates identical code to the previously mentioned reinterpret_cast method or union method for type punning . Examining the generated code we see it uses just register mov ( live Compiler Explorer Example ).

    C++20 and bit_cast

    In C++20 we may gain bit_cast ( implementation available in link from proposal ) which gives a simple and safe way to type-pun as well as being usable in a constexpr context.

    The following is an example of how to use bit_cast to type pun a unsigned int to float , ( see it live ):

     std::cout < < bit_cast(0x447a0000) < < "\n" ; //assuming sizeof(float) == sizeof(unsigned int) 

    In the case where To and From types don't have the same size, it requires us to use an intermediate struct15. We will use a struct containing a sizeof( unsigned int ) character array ( assumes 4 byte unsigned int ) to be the From type and unsigned int as the To type.:

     struct uint_chars { unsigned char arr[sizeof( unsigned int )] = {} ; // Assume sizeof( unsigned int ) == 4 }; // Assume len is a multiple of 4 int bar( unsigned char *p, size_t len ) { int result = 0; for( size_t index = 0; index < len; index += sizeof(unsigned int) ) { uint_chars f; std::memcpy( f.arr, &p[index], sizeof(unsigned int)); unsigned int result = bit_cast(f); result += foo( result ); } return result ; } 

    It is unfortunate that we need this intermediate type but that is the current constraint of bit_cast .

    Catching Strict Aliasing Violations

    We don't have a lot of good tools for catching strict aliasing in C++, the tools we have will catch some cases of strict aliasing violations and some cases of misaligned loads and stores.

    gcc using the flag -fstrict-aliasing and -Wstrict-aliasing can catch some cases although not without false positives/negatives. For example the following cases will generate a warning in gcc ( see it live ):

     int a = 1; short j; float f = 1.f; // Originally not initialized but tis-kernel caught // it was being accessed w/ an indeterminate value below printf("%i\n", j = *(reinterpret_cast(&a))); printf("%i\n", j = *(reinterpret_cast(&f))); 

    although it will not catch this additional case ( see it live ):

     int *p; p=&a; printf("%i\n", j = *(reinterpret_cast(p))); 

    Although clang allows these flags it apparently does not actually implement the warnings.

    Another tool we have available to us is ASan which can catch misaligned loads and stores. Although these are not directly strict aliasing violations they are a common result of strict aliasing violations. For example the following cases will generate runtime errors when built with clang using -fsanitize=address

     int *x = new int[2]; // 8 bytes: [0,7]. int *u = (int*)((char*)x + 6); // regardless of alignment of x this will not be an aligned address *u = 1; // Access to range [6-9] printf( "%d\n", *u ); // Access to range [6-9] 

    The last tool I will recommend is C++ specific and not strictly a tool but a coding practice, don't allow C-style casts. Both gcc and clang will produce a diagnostic for C-style casts using -Wold-style-cast . This will force any undefined type puns to use reinterpret_cast, in general reinterpret_cast should be a flag for closer code review. It is also easier to search your code base for reinterpret_cast to perform an audit.

    For C we have all the tools already covered and we also have tis-interpreter, a static analyzer that exhaustively analyzes a program for a large subset of the C language. Given a C verions of the earlier example where using -fstrict-aliasing misses one case ( see it live )

     int a = 1; short j; float f = 1.0 ; printf("%i\n", j = *((short*)&a)); printf("%i\n", j = *((int*)&f)); int *p; p=&a; printf("%i\n", j = *((short*)p)); 

    tis-interpeter is able to catch all three, the following example invokes tis-kernal as tis-interpreter (output is edited for brevity):

     ./bin/tis-kernel -sa example1.c ... example1.c:9:[sa] warning: The pointer (short *)(& a) has type short *. It violates strict aliasing rules by accessing a cell with effective type int. ... example1.c:10:[sa] warning: The pointer (int *)(& f) has type int *. It violates strict aliasing rules by accessing a cell with effective type float. Callstack: main ... example1.c:15:[sa] warning: The pointer (short *)p has type short *. It violates strict aliasing rules by accessing a cell with effective type int. 

    Finally there is TySan which is currently in development. This sanitizer adds type checking information in a shadow memory segment and checks accesses to see if they violate aliasing rules. The tool potentially should be able to catch all aliasing violations but may have a large run-time overhead.

    After reading many of the answers, I feel the need to add something:

    Strict aliasing (which I’ll describe in a bit) is important because :

    1. Memory access can be expensive (performance wise), which is why data is manipulated in CPU registers before being written back to the physical memory.

    2. If data in two different CPU registers will be written to the same memory space, we can’t predict which data will “survive” when we code in C.

      In assembly, where we code the loading and unloading of CPU registers manually, we will know which data remains intact. But C (thankfully) abstracts this detail away.

    Since two pointers can point to the same location in the memory, this could result in complex code that handles possible collisions .

    This extra code is slow and hurts performance since it performs extra memory read / write operations which are both slower and (possibly) unnecessary.

    The Strict aliasing rule allows us to avoid redundant machine code in cases in which it should be safe to assume that two pointers don’t point to the same memory block (see also the restrict keyword).

    The Strict aliasing states it’s safe to assume that pointers to different types point to different locations in the memory.

    If a compiler notices that two pointers point to different types (for example, an int * and a float * ), it will assume the memory address is different and it will not protect against memory address collisions, resulting in faster machine code.

    For example :

    Lets assume the following function:

     void merge_two_ints(int *a, int *b) { *b += *a; *a += *b; } 

    In order to handle the case in which a == b (both pointers point to the same memory), we need to order and test the way we load data from the memory to the CPU registers, so the code might end up like this:

    1. load a and b from memory.

    2. add a to b .

    3. save b and reload a .

      (save from CPU register to the memory and load from the memory to the CPU register).

    4. add b to a .

    5. save a (from the CPU register) to the memory.

    Step 3 is very slow because it needs to access the physical memory. However, it’s required to protect against instances where a and b point to the same memory address.

    Strict aliasing would allow us to prevent this by telling the compiler that these memory addresses are distinctly different (which, in this case, will allow even further optimization which can’t be performed if the pointers share a memory address).

    1. This can be told to the compiler in two ways, by using different types to point to. ie:

       void merge_two_numbers(int *a, long *b) {...} 
    2. Using the restrict keyword. ie:

       void merge_two_ints(int * restrict a, int * restrict b) {...} 

    Now, by satisfying the Strict Aliasing rule, step 3 can be avoided and the code will run significantly faster.

    In fact, by adding the restrict keyword, the whole function could be optimized to:

    1. load a and b from memory.

    2. add a to b .

    3. save result both to a and to b .

    This optimization couldn’t have been done before, because of the possible collision (where a and b would be tripled instead of doubled).

    Strict aliasing is not allowing different pointer types to the same data.

    This article should help you understand the issue in full detail.

    Technically in C++, the strict aliasing rule is probably never applicable.

    Note the definition of indirection ( * operator ):

    The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points .

    Also from the definition of glvalue

    A glvalue is an expression whose evaluation determines the identity of an object, (…snip)

    So in any well defined program trace, a glvalue refers to an object. So the so called strict aliasing rule doesn’t apply, ever. This may not be what the designers wanted.