45

次のコードを使用して、より大きなプログラムの一部としてファイルからデータを読み取りました。

double data_read(FILE *stream,int code) {
        char data[8];
        switch(code) {
        case 0x08:
            return (unsigned char)fgetc(stream);
        case 0x09:
            return (signed char)fgetc(stream);
        case 0x0b:
            data[1] = fgetc(stream);
            data[0] = fgetc(stream);
            return *(short*)data;
        case 0x0c:
            for(int i=3;i>=0;i--)
                data[i] = fgetc(stream);
            return *(int*)data;
        case 0x0d:
            for(int i=3;i>=0;i--)
                data[i] = fgetc(stream);
            return *(float*)data;
        case 0x0e:
            for(int i=7;i>=0;i--)
                data[i] = fgetc(stream);
            return *(double*)data;
        }
        die("data read failed");
        return 1;
    }

今、私は使用するように言われ、-O2次のgcc警告が表示されます: warning: dereferencing type-punned pointer will break strict-aliasing rules

Googleing私は2つの直交する答えを見つけました:

vs

結局、私は警告を無視したくありません。あなたは何をお勧めします?

【更新】おもちゃの例を実関数に置き換えました。

4

7 に答える 7

41

double*この問題は、 :を介してchar-arrayにアクセスするために発生します。

char data[8];
...
return *(double*)data;

ただし、gccは、プログラムが異なるタイプのポインターを介して変数にアクセスすることはないと想定しています。この仮定は厳密なエイリアシングと呼ばれ、コンパイラーがいくつかの最適化を行えるようにします。

*(double*)コンパイラが、と重複することは決してないことを知っている場合はdata[]、コードを次のように並べ替えるなど、あらゆる種類の処理が許可されます。

return *(double*)data;
for(int i=7;i>=0;i--)
    data[i] = fgetc(stream);

ループはおそらく最適化されており、最終的には次のようになります。

return *(double*)data;

これにより、data[]は初期化されません。この特定のケースでは、コンパイラーはポインターがオーバーラップしていることを認識できる可能性がありますが、char* dataそれを宣言している場合は、バグが発生している可能性があります。

ただし、厳密なエイリアシング規則では、char*とvoid*は任意のタイプを指すことができます。したがって、次のように書き直すことができます。

double data;
...
*(((char*)&data) + i) = fgetc(stream);
...
return data;

厳密なエイリアシング警告は、理解または修正するために非常に重要です。これらは、特定のマシン上の特定のオペレーティングシステム上の特定のコンパイラでのみ発生し、満月と年に1回のみ発生するため、社内で再現できない種類のバグを引き起こします。

于 2012-10-12T14:49:55.807 に答える
26

本当にfreadを使いたいように見えます。

int data;
fread(&data, sizeof(data), 1, stream);

とは言うものの、charを読み取るルートをたどり、それらをintとして再解釈したい場合、Cで( C ++ではなく)それを行う安全な方法は、ユニオンを使用することです。

union
{
    char theChars[4];
    int theInt;
} myunion;

for(int i=0; i<4; i++)
    myunion.theChars[i] = fgetc(stream);
return myunion.theInt;

元のコードの長さdataが3である理由はわかりません。4バイトが必要だったと思います。少なくとも、intが3バイトのシステムは知りません。

あなたのコードと私のコードはどちらも非常に移植性が低いことに注意してください。

編集:ファイルからさまざまな長さのintを移植したい場合は、次のようにしてみてください。

unsigned result=0;
for(int i=0; i<4; i++)
    result = (result << 8) | fgetc(stream);

(注:実際のプログラムでは、さらにfgetc()の戻り値をEOFに対してテストする必要があります。)

これは、システムのエンディアンに関係なく、ファイルから4バイトの符号なしをリトルエンディアン形式で読み取ります。unsignedが少なくとも4バイトであるほぼすべてのシステムで動作するはずです。

エンディアンニュートラルになりたい場合は、ポインタやユニオンを使用しないでください。代わりにビットシフトを使用してください。

于 2010-07-14T13:01:13.627 に答える
7

ユニオンを使用することは、ここで行う正しいことではありません。ユニオンの書き込まれていないメンバーからの読み取りは定義されていません。つまり、コンパイラーは、コードを壊す最適化を自由に実行できます(書き込みの最適化など)。

于 2010-12-22T18:31:52.010 に答える
7

このドキュメントは状況を要約しています:http://dbp-consulting.com/tutorials/StrictAliasing.html

そこにはいくつかの異なる解決策がありますが、最もポータブルで安全な解決策はmemcpy()を使用することです。(関数呼び出しは最適化されている可能性があるため、見た目ほど非効率的ではありません。)たとえば、次のように置き換えます。

return *(short*)data;

これとともに:

short temp;
memcpy(&temp, data, sizeof(temp));
return temp;
于 2016-04-28T16:42:59.400 に答える
3

基本的に、gccのメッセージは、問題を探している人として読むことができます。警告しなかったとは言わないでください

3バイトの文字配列をにキャストするintことは、私が今まで見た中で最悪のことの1つです。通常、あなたintは少なくとも4バイトを持っています。したがって、4番目(およびint幅が広い場合はそれ以上)の場合、ランダムなデータが得られます。そして、これらすべてをにキャストしますdouble

何もしないでください。gccが警告するエイリアシングの問題は、あなたがしていることに比べれば無害です。

于 2010-07-14T14:11:57.517 に答える
0

C標準の作成者は、理論的には可能であるが、グローバル変数が一見無関係なポインターを使用してその値にアクセスする可能性が低い状況で、コンパイラー作成者が効率的なコードを生成できるようにしたいと考えていました。アイデアは、単一の式でポインターをキャストおよび逆参照することによって型のパンニングを禁止することではなく、次のようなものを与えられたと言うことでした。

int x;
int foo(double *d)
{
  x++;
  *d=1234;
  return x;
}

コンパイラーは、*dへの書き込みがxに影響を与えないと想定する権利があります。標準の作成者は、未知のソースからポインターを受け取った上記のような関数が、型が完全に一致する必要なしに、一見無関係なグローバルをエイリアスする可能性があると想定する必要がある状況をリストしたいと考えていました。残念ながら、理論的根拠は、標準の作成者が、コンパイラがエイリアシングを引き起こす可能性があると信じる理由がない場合に最小限の適合性の標準を説明することを意図していることを強く示唆していますが、ルールは、コンパイラがエイリアシング認識することを要求していません。明らかですそして、gccの作成者は、実際に役立つコードを生成するのではなく、標準の不十分に記述された言語に準拠しながら、可能な限り最小のプログラムを生成し、それが明らかな場合にエイリアシングを認識するのではないことを決定しました(それでも、エイリアスが発生するように見えないものは、プログラマーが使用する必要があると想定することはできませんmemcpy。したがって、原因不明のポインターがほぼすべてのエイリアスを作成する可能性を考慮して、コンパイラーが必要になります。 、したがって最適化を妨げます。

于 2016-04-13T21:04:05.943 に答える
-4

どうやら、標準ではsizeof(char *)をsizeof(int *)とは異なるものにすることが許可されているため、直接キャストしようとするとgccが文句を言います。void *は、すべてがvoid*との間でやり取りできるという点で少し特別です。実際には、ポインタがすべてのタイプで常に同じであるとは限らないアーキテクチャ/コンパイラの多くはわかりませんが、gccは、煩わしい場合でも警告を発するのが正しいです。

安全な方法は

int i, *p = &i;
char *q = (char*)&p[0];

また

char *q = (char*)(void*)p;

これを試して、何が得られるかを確認することもできます。

char *q = reinterpret_cast<char*>(p);
于 2010-08-16T08:25:20.483 に答える