厳密なエイリアシングによるバグがいくつかあったので、それらすべてを修正しようと思いました。それが何であるかを詳細に調べたところ、GCCが警告を発行しない場合があり、実装できないものもあるようです。少なくとも私の理解では、以下のすべてが壊れています。私の理解は間違っていますか、これらすべてのことを行う正しい方法はありますか、それとも技術的にルールを破ってシステムテストで十分にカバーする必要があるコードがありますか?
バグは、以下のように、char バッファーと unsigned char バッファーが混在しているコードに起因していました。
size_t Process(char *buf, char *end)
{
char *p = buf;
ProcessSome((unsigned char**)&p, (unsigned char*)end);
//GCC decided p could not be changed by ProcessSome and so always returned 0
return (size_t)(p - buf);
}
これを以下に変更すると問題が解決したようですが、まだキャストが含まれているため、なぜこれが機能し、警告が表示されないのかわかりません。
size_t Process(char *buf, char *end)
{
unsigned char *buf2 = (unsigned char *)buf;
unsigned char *p = buf2;
unsigned char *end2 = (unsigned char*)end;
ProcessSome(&p, end2);
return (size_t)(p - buf2);
}
また、警告なしで動作するように見える他の場所がたくさんあります
//contains a unsigned char* of data. Possibly from the network, disk, etc.
//the buffer contents itself is 8 byte aligned.
const Buffer *buffer = foo();
const uint16_t *utf16Text = (const uint16_t*)buffer->GetData();//const unsigned char*
//... read utf16Text. Does not even seem to ever be a warning
//also seems to work fine
size_t len = CalculateWorstCaseLength(...);
Buffer *buffer = new Buffer(len * 2);
uint16_t *utf16 = (uint16_t*)buffer->GetData();//unsigned char*
len = DoSomeProcessing(utf16, len, ...);
buffer->Truncate(len * 2);
send(buffer);
そして、いくつかの...
struct Hash128
{
unsigned char data[16];
};
...
size_t operator ()(const Hash128 &hash)
{
return *(size_t*)hash.data;//warning
}
非チャーケース。これには警告がありません。たとえそれが悪いとしても、どうすれば回避できますか (どちらの方法でもうまくいくようです)。
int *x = fromsomewhere();//aligned to 16 bytes, array of 4
__m128i xmm = _mm_load_si128((__m128*i)x);
__m128i xmm2 = *(__m128i*)x;
他の API を見ると、私の理解では、ルールに違反しているさまざまなケースがあるようです (Linux/GCC 固有のものに遭遇したことはありませんが、どこかに必ずあるはずです)。
CoCreateInstance 明示的なポインター キャストを必要とする void** 出力パラメーターがあります。Direct3D にもこのようなものがあります。
LARGE_INTEGER は、さまざまなメンバーに対して読み取り/書き込みを行う可能性が高い共用体です (たとえば、一部のコードは高/低を使用し、他のコードは int64 を読み取る可能性があります)。
CPython の実装は、PyObject* を、たまたま最初に同じメモリ レイアウトを持つ他の多くのものに非常に喜んでキャストしたことを思い出します。
私が見た多くのハッシュ実装は、入力バッファーを uint32_t* にキャストし、おそらく uint8_t を使用して最後に 1 ~ 3 バイトを処理します。
私が見たほとんどすべてのメモリ アロケータの実装では、char* または unsigned char* を使用しています。これらは、目的の型にキャストする必要があります (おそらく、返された void* を介してですが、内部的には少なくとも char でした)。