厳密なエイリアシングルールとは何ですか?

827
Benoit 2008-09-19 15:30.

Cでの一般的な未定義の動作について尋ねるとき、人々は厳密なエイリアシングルールを参照することがあります。
彼らは何を話している?

10 answers

571
Doug T. 2008-09-19 16:36.

厳密なエイリアシングの問題が発生する一般的な状況は、構造体(デバイス/ネットワークメッセージなど)をシステムのワードサイズのバッファー(uint32_tsまたはuint16_tsへのポインターなど)にオーバーレイする場合です。構造体をそのようなバッファにオーバーレイする場合、またはポインタキャストを介してそのような構造体にバッファをオーバーレイする場合、厳密なエイリアシングルールに簡単に違反する可能性があります。

したがって、この種のセットアップでは、何かにメッセージを送信する場合、同じメモリチャンクを指す2つの互換性のないポインタが必要になります。次に、次のようなものを単純にコーディングします。

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]);   
    }
}

厳密なエイリアシング規則は、この設定が不正になり:別名のないオブジェクトことポインタを逆参照互換型又はC 2011 6.5項7によって許可され、他のタイプのいずれか1未定義の動作です。残念ながら、あなたはまだ、この方法をコーディングすることができます多分あなたは、コードを実行したときにのみ、奇妙な予期しない動作を持っている、それは罰金をコンパイルする必要があり、いくつかの警告を取得します。

(GCCは、エイリアシング警告を出す能力に多少一貫性がないように見えます。友好的な警告を与えることもあれば、そうでないこともあります。)

この動作が定義されていない理由を確認するには、厳密なエイリアシングルールがコンパイラを購入するものについて考える必要があります。基本的に、このルールでbuffは、ループのすべての実行の内容を更新するための命令の挿入について考える必要はありません。代わりに、エイリアシングに関するいくつかの厄介な強制されていない仮定を使用して最適化する場合、ループが実行される前に一度CPUレジスタにロードbuff[0]してbuff[1]を省略し、ループの本体を高速化できます。厳密なエイリアシングが導入される前は、コンパイラはbuff、いつでもどこからでも誰でも内容が変更される可能性があるという妄想状態にある必要がありました。そのため、パフォーマンスをさらに向上させるために、ほとんどの人がポインターを型のパンニングしないと仮定して、厳密なエイリアシングルールが導入されました。

例が考案されていると思われる場合は、送信を行う別の関数にバッファを渡している場合でも、そうなる可能性があることに注意してください。

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

そして、この便利な機能を利用するために、以前のループを書き直しました

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

コンパイラーは、SendMessageをインライン化しようとするのに十分な能力がある場合とできない場合があり、バフを再度ロードするかどうかを決定する場合としない場合があります。SendMessageが個別にコンパイルされた別のAPIの一部である場合、おそらくbuffのコンテンツをロードするための命令があります。繰り返しになりますが、C ++を使用している場合、これはテンプレート化されたヘッダーのみの実装であり、コンパイラーはインライン化できると考えています。あるいは、自分の便宜のために.cファイルに書き込んだものかもしれません。とにかく、未定義の振る舞いがまだ続くかもしれません。内部で何が起こっているかを知っている場合でも、それはルール違反であるため、明確に定義された動作は保証されません。したがって、単語で区切られたバッファを使用する関数でラップするだけでは、必ずしも役立つとは限りません。

では、どうすればこれを回避できますか?

  • ユニオンを使用します。ほとんどのコンパイラは、厳密なエイリアシングについて文句を言うことなくこれをサポートします。これはC99で許可されており、C11で明示的に許可されています。

      union {
          Msg msg;
          unsigned int asBuffer[sizeof(Msg)/sizeof(unsigned int)];
      };
    
  • コンパイラで厳密なエイリアシングを無効にすることができます(gccではf [no-] strict-aliasing))

  • char*システムの単語の代わりにエイリアシングに使用できます。ルールでは、char*signed charおよびを含むunsigned char)の例外が許可されます。char*エイリアスは他のタイプであると常に想定されています。ただし、これは他の方法では機能しません。構造体が文字のバッファをエイリアスするという仮定はありません。

初心者は注意してください

これは、2つのタイプを互いにオーバーレイする場合の1つの潜在的な地雷原にすぎません。また、エンディアン、ワードアラインメント、および構造体を正しくパックすることでアラインメントの問題に対処する方法についても学ぶ必要があります。

脚注

1 C 2011 6.57で左辺値へのアクセスが許可されるタイプは次のとおりです。

  • オブジェクトの有効なタイプと互換性のあるタイプ、
  • オブジェクトの有効なタイプと互換性のあるタイプの修飾バージョン、
  • オブジェクトの有効なタイプに対応する符号付きまたは符号なしのタイプであるタイプ、
  • オブジェクトの有効な型の修飾バージョンに対応する符号付きまたは符号なしの型である型。
  • メンバー内に前述のタイプの1つを含む集合体または共用体タイプ(再帰的に、サブ集合体または含まれる共用体のメンバーを含む)、または
  • 文字タイプ。
244
Niall 2008-09-19 15:38.

私が見つけた最も良い説明は、Mike Acton、Understanding StrictAliasingによるものです。PS3の開発に少し焦点を当てていますが、それは基本的にはGCCだけです。

記事から:

「厳密なエイリアシングは、C(またはC ++)コンパイラによって行われた、異なるタイプのオブジェクトへのポインタの逆参照が同じメモリ位置を参照することは決してない(つまり、互いにエイリアスする)という仮定です。」

したがって、基本的に、をint*含むメモリを指している場合、そのメモリを指しint、それをルール違反float*として使用しfloatます。コードがこれを尊重しない場合、コンパイラのオプティマイザがコードを破壊する可能性があります。

ルールの例外は、char*であり、これは任意のタイプを指すことができます。

137
Ben Voigt 2011-08-10 18:43.

これは、C ++ 03標準のセクション3.10にある厳密なエイリアシングルールです(他の回答は適切な説明を提供しますが、ルール自体を提供するものはありません)。

プログラムが次のタイプのいずれか以外の左辺値を介してオブジェクトの格納された値にアクセスしようとした場合、動作は定義されていません。

  • オブジェクトの動的タイプ、
  • オブジェクトの動的タイプのcv修飾バージョン。
  • オブジェクトの動的タイプに対応する符号付きまたは符号なしタイプであるタイプ。
  • オブジェクトの動的タイプのcv修飾バージョンに対応する符号付きまたは符号なしタイプであるタイプ。
  • メンバー内に前述のタイプの1つを含む集合体または共用体タイプ(再帰的に、サブ集合体または含まれる共用体のメンバーを含む)、
  • オブジェクトの動的型の(おそらくcv修飾された)基本クラス型である型。
  • Acharまたはunsigned charタイプ。

C ++ 11およびC ++ 14の文言(変更が強調されています):

プログラムが次のタイプのいずれか以外のglvalueを介してオブジェクトの格納された値にアクセスしようとした場合、動作は定義されていません。

  • オブジェクトの動的タイプ、
  • オブジェクトの動的タイプのcv修飾バージョン。
  • オブジェクトの動的タイプに類似したタイプ(4.4で定義)、
  • オブジェクトの動的タイプに対応する符号付きまたは符号なしタイプであるタイプ。
  • オブジェクトの動的タイプのcv修飾バージョンに対応する符号付きまたは符号なしタイプであるタイプ。
  • そのうち、前述のタイプのうちの1つを含む凝集体または共用タイプの要素または非静的データメンバ(を含むが、再帰的に要素又は非静的データメンバsubaggregateまたは含ま連合)、
  • オブジェクトの動的型の(おそらくcv修飾された)基本クラス型である型。
  • Acharまたはunsigned charタイプ。

2つの変更は小さかった:左辺値の代わりにglvalue、および集計/結合の場合の明確化。

3番目の変更により、より強力な保証が作成されます(強力なエイリアシングルールが緩和されます)。エイリアスが安全になった同様のタイプの新しい概念。


また、Cの文言(C99; ISO / IEC 9899:1999 6.5 / 7; ISO / IEC 9899:2011§6.5¶7でもまったく同じ文言が使用されています):

オブジェクトは、次のタイプ73)または88)のいずれかを持つ左辺値式によってのみアクセスされる格納された値を持つものとします。

  • オブジェクトの有効なタイプと互換性のあるタイプ、
  • オブジェクトの有効なタイプと互換性のあるタイプの修飾バージョン、
  • オブジェクトの有効なタイプに対応する符号付きまたは符号なしのタイプであるタイプ、
  • オブジェクトの有効な型の修飾バージョンに対応する符号付きまたは符号なしの型である型。
  • メンバー内に前述のタイプの1つを含む集合体または共用体タイプ(再帰的に、サブ集合体または含まれる共用体のメンバーを含む)、または
  • 文字タイプ。

73)または88)このリストの目的は、オブジェクトがエイリアスされる場合とされない場合がある状況を指定することです。

93
Shafik Yaghmour 2018-07-08 16:07.

注意

これは、私の「厳密なエイリアシングルールとは何ですか。なぜ気にするのですか?」から抜粋したものです。書き上げる。

厳密なエイリアシングとは何ですか?

CおよびC ++では、エイリアシングは、格納されている値にアクセスできる式の種類と関係があります。CとC ++の両方で、標準はどの式タイプがどのタイプをエイリアスできるかを指定します。コンパイラとオプティマイザは、エイリアシングルールに厳密に従っていると想定できるため、厳密なエイリアシングルールという用語が使用されます。許可されていないタイプを使用して値にアクセスしようとすると、未定義の動作(UB)として分類されます。未定義の動作が発生すると、すべての賭けが無効になり、プログラムの結果は信頼できなくなります。

残念ながら、厳密なエイリアシング違反があると、期待どおりの結果が得られることが多く、新しい最適化を備えたコンパイラの将来のバージョンでは、有効と思われるコードが破損する可能性があります。これは望ましくなく、厳密なエイリアシングルールとそれらに違反しないようにする方法を理解することは価値のある目標です。

なぜ私たちが気にするのかを理解するために、型のパンニングで使用される一般的な手法は厳密なエイリアスルールに違反することが多いため、厳密なエイリアスルール、型のパンニングに違反した場合に発生する問題と、正しく入力する方法について説明します。

予備的な例

いくつかの例を見てみましょう。次に、標準の内容を正確に説明し、さらにいくつかの例を調べて、厳密なエイリアシングを回避し、見逃した違反をキャッチする方法を確認します。これは驚くべきことではない例です(ライブ例):

int x = 10;
int *ip = &x;

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

我々は持っているのint *メモリを指すが、によって占めint型とこれが有効なエイリアスです。オプティマイザは、ipを介した割り当てによってxが占める値が更新される可能性があると想定する必要があります。

The next example shows aliasing that leads to undefined behavior (live example):

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<float*>(&x), &x);
    std::cout << x << "\n";   // Expect 0?
}

In the function foo we take an int* and a float*, in this example we call foo and set both parameters to point to the same memory location which in this example contains an int. Note, the reinterpret_cast is telling the compiler to treat the the expression as if it had the type specificed by its template parameter. In this case we are telling it to treat the expression &x as if it had type float*. We may naively expect the result of the second cout to be 0 but with optimization enabled using -O2 both gcc and clang produce the following result:

0
1

Which may not be expected but is perfectly valid since we have invoked undefined behavior. A float can not validly alias an int object. Therefore the optimizer can assume the constant 1 stored when dereferencing i will be the return value since a store through f could not validly affect an int object. Plugging the code in Compiler Explorer shows this is exactly what is happening(live example):

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

The optimizer using Type-Based Alias Analysis (TBAA) assumes 1 will be returned and directly moves the constant value into register eax which carries the return value. TBAA uses the languages rules about what types are allowed to alias to optimize loads and stores. In this case TBAA knows that a float can not alias and int and optimizes away the load of i.

Now, to the Rule-Book

What exactly does the standard say we are allowed and not allowed to do? The standard language is not straightforward, so for each item I will try to provide code examples that demonstrates the meaning.

What does the C11 standard say?

The C11 standard says the following in section 6.5 Expressions paragraph 7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88) — a type compatible with the effective type of the 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

— a qualified version of a type compatible with the effective type of the 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

— a type that is the signed or unsigned type corresponding to the effective type of the 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

gcc/clang has an extension and also that allows assigning unsigned int* to int* even though they are not compatible types.

— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the 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

— 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

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 );

— a character type.

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.

What the C++17 Draft Standard say

The C++17 draft standard in section [basic.lval] paragraph 11 says:

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, f.x ); 

(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 ) {
  f.x = 1;
  b.x = 2;

  return f.x;
}

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

int foo( std::byte &b, uint32_t &ui ) {
  b = static_cast<std::byte>('a');
  ui = 0xFFFFFFFF;                   

  return std::to_integer<int>( 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. For example:

int x =  1 ;

// In C
float *fp = (float*)&x ;  // Not a valid aliasing

// In C++
float *fp = reinterpret_cast<float*>(&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;
u.f = 1.0f;

printf( "%d\n”, u.n );  // 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<float>(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<unsigned int>(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<short*>(&a)));
printf("%i\n", j = *(reinterpret_cast<int*>(&f)));

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

int *p;

p=&a;
printf("%i\n", j = *(reinterpret_cast<short*>(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.

44
phorgan1 2011-06-20 13:46.

Strict aliasing doesn't refer only to pointers, it affects references as well, I wrote a paper about it for the boost developer wiki and it was so well received that I turned it into a page on my consulting web site. It explains completely what it is, why it confuses people so much and what to do about it. Strict Aliasing White Paper. In particular it explains why unions are risky behavior for C++, and why using memcpy is the only fix portable across both C and C++. Hope this is helpful.

34
Ingo Blackman 2013-05-14 16:37.

As addendum to what Doug T. already wrote, here is a simple test case which probably triggers it with gcc :

check.c

#include <stdio.h>

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 with gcc -O2 -o check check.c . Usually (with most gcc versions I tried) this outputs "strict aliasing problem", because the compiler assumes that "h" cannot be the same address as "k" in the "check" function. Because of that the compiler optimizes the if (*h == 5) away and always calls the printf.

For those who are interested here is the x64 assembler code, produced by gcc 4.6.3, running on ubuntu 12.04.2 for x64:

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

So the if condition is completely gone from the assembler code.

18
supercat 2017-04-27 12:42.

According to the C89 rationale, the authors of the Standard did not want to require that compilers given code like:

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

should be required to reload the value of x between the assignment and return statement so as to allow for the possibility that p might point to x, and the assignment to *p might consequently alter the value of x. The notion that a compiler should be entitled to presume that there won't be aliasing in situations like the above was non-controversial.

Unfortunately, the authors of the C89 wrote their rule in a way that, if read literally, would make even the following function invoke Undefined Behavior:

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

because it uses an lvalue of type int to access an object of type struct S, and int is not among the types that may be used accessing a struct S. Because it would be absurd to treat all use of non-character-type members of structs and unions as Undefined Behavior, almost everyone recognizes that there are at least some circumstances where an lvalue of one type may be used to access an object of another type. Unfortunately, the C Standards Committee has failed to define what those circumstances are.

Much of the problem is a result of Defect Report #028, which asked about the behavior of a program like:

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);
}

Defect Report #28 states that the program invokes Undefined Behavior because the action of writing a union member of type "double" and reading one of type "int" invokes Implementation-Defined behavior. Such reasoning is nonsensical, but forms the basis for the Effective Type rules which needlessly complicate the language while doing nothing to address the original problem.

The best way to resolve the original problem would probably be to treat the footnote about the purpose of the rule as though it were normative, and made the rule unenforceable except in cases which actually involve conflicting accesses using aliases. Given something like:

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

There's no conflict within inc_int because all accesses to the storage accessed through *p are done with an lvalue of type int, and there's no conflict in test because p is visibly derived from a struct S, and by the next time s is used, all accesses to that storage that will ever be made through p will have already happened.

If the code were changed slightly...

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

Here, there is an aliasing conflict between p and the access to s.x on the marked line because at that point in execution another reference exists that will be used to access the same storage.

Had Defect Report 028 said the original example invoked UB because of the overlap between the creation and use of the two pointers, that would have made things a lot more clear without having to add "Effective Types" or other such complexity.

17
Chris Jester-Young 2008-09-19 15:38.

Type punning via pointer casts (as opposed to using a union) is a major example of breaking strict aliasing.

11
Myst 2017-12-25 02:04.

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. i.e.:

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

    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).

6
Jason Dagit 2008-09-19 15:33.

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

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

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

ねえNFL、ジョーバロウとカイラーマレーは女性の権利をサポートするために少しの助けを使うことができます

ねえNFL、ジョーバロウとカイラーマレーは女性の権利をサポートするために少しの助けを使うことができます

ジョー・バロウロー対ウェイド事件の転覆に対応するNFLは、言葉を言わないことで、腹立たしいが予測可能なPRの結果でした。

別の日、別のヒンジのないLIV記者会見

別の日、別のヒンジのないLIV記者会見

(lから)パット・ペレス、ブルックス・ケプカ、パトリック・リードサウジアラビアのLIVゴルフリーグのさらに別の信じられないほどの記者会見で、スポーツのファンはブルックス・ケプカからでたらめな吐き気と質問回避の驚異的なマスタークラスを受けました。パトリックリード、ブライソンデシャンボー、パットペレス、最近のPGAツアーの脱北者。

ミズ・マーベルの家族の帰郷は悪役よりも激しく打撃を与える

ミズ・マーベルの家族の帰郷は悪役よりも激しく打撃を与える

レッドダガーとマーベルさんがチームを組んでいます。

6億7500万ドルのビットコインローンのデフォルト後にすべての資産を清算するように命じられたスリーアローズキャピタル

6億7500万ドルのビットコインローンのデフォルト後にすべての資産を清算するように命じられたスリーアローズキャピタル

暗号業界最大の沈没船の1つであるスリーアローズキャピタルは、ついにその悲惨さから解放されています。火曜日に、不良債権ヘッジファンドは、債権者からの返済を要求する訴訟の高まりに応えて、バージンアイランド裁判所によって清算を命じられました彼らが3ACに行ったローン。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

Suffragettes Indicam #3: Junho

Suffragettes Indicam #3: Junho

Mais um mês se findando — e metade do ano de 2022 já passou. Sabe o que isso significa? Não, não é hora de verificar se você está cumprindo com suas resoluções de Ano Novo.

多元宇宙—Junø

多元宇宙—Junø

チェーン間アカウントがJunoに登場します。異なるブロックチェーン間でスマートコントラクトの構成可能性と真の相互運用性を提供します。

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

1.ドラマを見た後、起業する考えはありますか?あなたのビジネスはボトルネックに遭遇しましたか?方向性がなくてわからない場合は、ドラマを追いかけて行くことを心からお勧めします。(?)ブラフではなく、最も完璧なビジネス例を隠すドラマがあります。2.ブレイキング・バッドとその弁護士ドラマ「ブレイキング・バッド」を見た友人たちは、演劇の中で、穏やかな表情で、弁護士のソウル・グッドマンに深く感銘を受けなければなりません。口を開けて、感覚の弱い傭兵の性格を持っています。道徳の面で、サル・グッドマンは無意識のうちに劇に欠かせない役割を果たし、彼自身のシリーズ「絶望的な弁護士」(ベター・コール・ソール)を生み出しました。ウェントウのテキストとビデオは、劇中のソウル・グッドマンのテレビコマーシャルです。製品(サービス)、競争戦略、市場ポジショニング、ブランド名、ターゲット顧客グループ、コミュニケーション軸から広告まで、サル・グッドマンの役割のビジネス設定は、「最低」と見なすことができる超超超超超超完全です。ブランドコミュニケーションのコスト」「変化」のモデル。なぜ?私の分析をご覧ください。3.ソウル・グッドマンの「事業戦略」1.基本情報ブランド名:Saul Goodman製品:法律相談サービス対象顧客:麻薬中毒、飲酒運転、事故など。法律知識の欠如は、一般的に公立弁護士にしか余裕がなく、真面目な弁護士も「特別な法律を持つ消費者」を避けます。恐れてはいけない「​​ニーズ」。コミュニケーションの主軸:この国のすべての男性、女性、子供は有罪判決を受けるまで無実だと思います。地域:アルバカーキ市スローガン:Thrallに電話したほうがいいです!(ベター・コール・ソール)広告:2つの可能性のある犯罪状況をシミュレートします+サウルの主張+サウルのスローガン2をより適切に呼び出します。

メインネットガイド— Arbitrum Odyssey Week 2

メインネットガイド— Arbitrum Odyssey Week 2

最新のアップデートを受け取るために私たちに従ってください。ニュースレター:https://www。

Language