2013-07-18 13 views
5

厳密なエイリアシングから2つのバグがあり、すべてを修正しようと思っていました。それが何であるかを詳しく見てみると、GCCは警告を出しませんし、実装が不可能なものもあるようです。少なくとも私の理解では、以下のすべてが壊れています。私の理解は間違っているか、これらのことを行う正しい方法があるのでしょうか、あるいは技術的にルールを破ってシステムテストで十分にカバーしなければならないコードがありますか?厳密なエイリアシングは矛盾しているようです

バグは、charとunsigned charバッファが混在しているコードからのものです。以下のように:

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

もあります:それはまだ、これが機能するようになりましたし、自由に警告している理由を私は確認していないキャストを必要とするが、以下にこれを変更する

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

は、問題を修正するように見えました警告

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

そして

...といくつかなくても動作するように見える他の場所の束であります

大文字小文字の区別がありません。これには警告はありません。悪い場合でも、どうすれば回避できますか(どちらもうまくいくようです)?

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 specfic 1に遭遇していませんが、必ずどこかに1が存在することになります)。

  1. CoCreateInstance明示的なポインタキャストを必要とするvoid **出力パラメータがあります。 Direct3Dにも同様のものがあります。

  2. LARGE_INTEGERは、異なるメンバーへの読み書きが可能な組み合わせです(たとえば、一部のコードが高低を使用している可能性があり、他のコードがint64を読み取る可能性があります)。

  3. 私は、CPythonの実装が非常にうまくいけば、最初に同じメモリレイアウトを持つ他のものにPyObject *をキャストすることを思い出してください。

  4. 私が見たハッシュ実装の多くは、入力バッファをuint32_t *にキャストし、おそらくuint8_tを使用して最後に1-3バイトを処理します。

  5. 私が見たほとんどのメモリアロケータの実装は、char *またはunsigned char *を使用していますが、必要な型にキャストする必要があります(返されたvoid *を使用していますが、 )

+0

は、実際にあなたの最初の例をい常にゼロを返すという振る舞いを呈しますか、それともコードに似ていますか?私はその行動を再現できませんでした。 –

+0

かなり正確には、ちょうど異なる関数名です。これは、x64を対象とした変更された企業の赤い帽子でgcc 4.4.5でした。しかし、全体のロットがインライン化されているので、GCCがどのように全体を最適化するかということに非常に特化しているかもしれません。 – Will

答えて

4

最初に、charunsigned charへのポインタは、ほとんど文字列エイリアシングに関する規則を除いて です。 はchar*またはunsigned char*に任意のタイプのポインタを変換することができ、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); 
} 

ここでの問題は、それがunsigned char*char* かのように見しようとしているということです。それはではなく、が保証されています。 にキャストがはっきりと見える場合、g ++は自動的に の厳密なエイリアシング解析をオフにしないように少し鈍い ですが、技術的には標準によってカバーされています。一方、

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

、変換のすべては何を別名があり、どちらも、char*unsigned char*を伴うので、 コンパイラは、この作業を行う必要があります。

残りについては、戻り値の型が buffer->GetData()であるとは言わないので、言うことは難しいです。しかし、 char*unsigned char*またはvoid*の場合、コードは完全に有効です (ただし、2番目の使用でキャストが見つからない場合は buffer->GetData())。元のポインタが これらのタイプのうちの1つを有する場合、:キャストのすべて char*を含む限り、unsigned char*又はvoid*const 修飾子を無視する)は、コンパイラは が可能エイリアシングが存在すると仮定することが要求されます からターゲット型へのポインタをキャストすることで作成できました。また、 は、任意のポインタをこれらの型の1つに変換して元の型の に戻すことができます。最後の例に関しては

char*はもともとuint16_t、あなたは 位置合わせの問題で終わるかもしれませんが、コンパイラは、一般的にこのことを知ることができない。でなかった場合はもちろん、)、あなたはしないでください hash.dataのタイプを示しているので、言うことは難しいです。それはchar*void*または unsigned char*であれば、言語はあなたのコード を保証(技術的には、char型のポインタがsize_t*を変換 によって作成されたものとする。実際には、 ポインタが十分に揃っていることを提供し、バイトに尖っはしないでください は、size_tのトラッピング値を形成します)。

一般に、「型打ち」の唯一の保証された方法は で、memcpyです。そうでなければ、 のようなポインタキャストは、少なくともである限り、void*, char*またはunsigned char*にある限り、保証されます。 (これらのいずれかからアライメントが発生する可能性があります。 参照を逆参照すると、トラッピング値にアクセスすることがあります)。 規格から追加保証を受ける場合があります。 Posixには次のようなものが必要です。

void (*pf)(); 
*((void**)&pf) = ... 

などが必要です。 (あなたはエイリアシングが関連するかもしれない機能に 他に何もしない場合は一般的に、すぐに をキャストし、逆参照することさえグラム++、で、動作します。)

そして、私が知っているコンパイラのすべてが使用できるようになりますa union の場合は、いくつかの時間の間、 (unionが表示されていない場合 G ++、その他の場合にはunionの法的な用途で失敗します含む少なくとも一部、。 が正しくunionを処理することは、コンパイラライター ため注意が必要です。)

+0

私はいつもエイリアシングルールを把握できる人を畏敬の念にしています。 +1: – jalf

+0

バッファには常に符号なし文字配列が含まれています。 GetDataは(const)unsigned char *を返します。ハッシュは、unsigned char data [16]を持つ単なる構造体です。コードサンプルを更新しました。 CPythonの場合、ソケットアドレスはsockaddrと同様のことを思い出します。 – Will

+0

コンパイラは最初の例で 'p'を変更できないと仮定することができますか? '&p'が' unsigned char ** 'にキャストされていても、コンパイラは' ProcessSome'の呼び出し中にそのポインタが 'char **'にキャストされることはないと仮定できますか? –

0

char/unsigned charポインタ厳密なエイリアシング規則から免除されます。

ユニオントリックは技術的にエイリアシングエラーですが、主流のコンパイラは明示的に許可しています。

例の中にはいくつかの例があります(また、言語によってはUBがいくつかありますが、コンパイラによって適切に定義されています)。

しかし、はい、エイリアシングルールに違反するコードがたくさんあります。また、MSVCは厳密なエイリアシングに基づいて最適化を行わないため、Windows用に作成されたコードは厳密なエイリアシング規則に違反する可能性があります。

+0

もしchar/unsigned charが常に免除されれば(私はそれがT * * char * castであったとは思っていませんでした)、128-> size_tハッシュ変換とオリジナルのProcessリリースビルドで壊れましたか?)それは本当にUBですか? – Will

+0

すべての良い質問。私はエイリアシングのルールを心に唱えることはできません。あなたが知っているように、彼らはひどく複雑です。 :) ごめんなさい。私が覚えていれば、後でそれを調べようとします – jalf

+0

GCCの厳密なエイリアシングの警告*が矛盾していることは指摘する価値があります。すべてのエイリアシング違反を正確に検出することは計算上実行不可能です。そこでGCCは検出できるケースについて警告しますが、エイリアス警告が表示されないということは、コードにエイリアス違反がないことを意味するものではありません。 – jalf

関連する問題