10

次のコードをテストします。

#include <stdio.h>
#include <stdlib.h>
main()
{
    const char *yytext="0";
    const float f=(float)atof(yytext);
    size_t t = *((size_t*)&f);
    printf("t should be 0 but is %d\n", t);
}

次のようにコンパイルします。

gcc -O3 test.c

GOOD 出力は次のようになります。

"t should be 0 but is 0"

しかし、私のgcc 4.1.3では、次のものがあります。

"t should be 0 but is -1209357172"
4

9 に答える 9

18

コンパイラ フラグ -fno-strict-aliasing を使用します。

デフォルトでは少なくとも -O3 であるため、厳密なエイリアシングを有効にすると、次の行で:

size_t t = *((size_t*)&f);

コンパイラは、size_t* が float* と同じメモリ領域を指していないと想定します。私の知る限り、これは標準に準拠した動作です (Thomas Kammeyer が指摘したように、ANSI 標準の厳格なエイリアシング規則への準拠は gcc-4 あたりから始まります)。

私の記憶が正しければ、これを回避するために char* への中間キャストを使用できます。(コンパイラは char* が何でもエイリアスできると仮定します)

言い換えれば、これを試してください(今は自分でテストすることはできませんが、うまくいくと思います):

size_t t = *((size_t*)(char*)&f);
于 2008-09-17T14:44:55.080 に答える
6

C99標準では、これは6.5-7の次のルールでカバーされています。

オブジェクトは、次のいずれかのタイプの左辺値式によってのみ、格納された値にアクセスする必要があります。73)

  • オブジェクトの有効なタイプと互換性のあるタイプ、

  • オブジェクトの有効なタイプと互換性のあるタイプの修飾バージョン、

  • オブジェクトの有効な型に対応する符号付きまたは符号なしの型である型、

  • オブジェクトの有効な型の修飾バージョンに対応する符号付きまたは符号なしの型である型、

  • メンバーの中に前述のタイプの1つを含む集合体または共用体タイプ(再帰的に、サブ集合体または含まれる共用体のメンバーを含む)、または

  • 文字タイプ。

最後の項目は、最初に(char *)にキャストすることが機能する理由です。

于 2008-09-17T15:08:05.370 に答える
5

それは悪いCコードです:-)

問題の部分は、float 型の 1 つのオブジェクトに、整数ポインターにキャストして逆参照することでアクセスすることです。

これはエイリアシング規則に違反しています。コンパイラは、float や int などの異なる型へのポインターがメモリ内でオーバーラップしないことを自由に想定できます。あなたはまさにそれをしました。

コンパイラが認識しているのは、何かを計算し、それを float f に格納し、それ以上アクセスしないということです。ほとんどの場合、コンパイラはコードの一部を削除し、割り当ては行われませんでした。

この場合、 size_t ポインターを介した逆参照は、スタックから初期化されていないガベージを返します。

これを回避するには、次の 2 つの方法があります。

  1. float と size_t メンバーでユニオンを使用し、型パニングを介してキャストを行います。良くありませんが、機能します。

  2. memcopy を使用して f の内容を size_t にコピーします。コンパイラは、このケースを検出して最適化するのに十分スマートです。

于 2008-09-17T14:50:28.983 に答える
5

これは、ポインターのエイリアシングに関する C99 規則に従って許可されなくなりました。2 つの異なる型のポインターは、メモリ内の同じ場所を指すことはできません。この規則の例外は、void および char ポインターです。

したがって、size_t のポインターにキャストするコードでは、コンパイラーはこれを無視することを選択できます。float 値を size_t として取得する場合は、それを割り当てるだけで、float は次のようにキャストされます (丸められずに切り捨てられます)。

size_t size = (size_t)(f); //これは機能します

これは一般的にバグとして報告されますが、実際には、オプティマイザがより効率的に動作できるようにする機能です。

gcc では、コンパイラ スイッチを使用してこれを無効にすることができます。-fno_strict_aliasing だと思います。

于 2008-09-17T14:47:38.553 に答える
3

なぜ t は 0 であるべきだと思いますか?

または、より正確に言えば、「浮動小数点ゼロのバイナリ表現が整数ゼロのバイナリ表現と同じになると思うのはなぜですか?」

于 2008-09-17T14:45:24.393 に答える
1

ポインターの配置は別として、sizeof(size_t)== sizeof(float)であることが期待されます。私はそうではないと思います(64ビットLinuxではsize_tは64ビットである必要がありますが、32ビットでフロートします)。つまり、コードは初期化されていないものを読み取ります。

于 2009-04-06T07:23:33.137 に答える
1

これは悪い C コードです。あなたのキャストは C のエイリアシング ルールに違反しており、オプティマイザはこのコードを壊すことを自由に行うことができます。おそらく、GCC が浮動小数点書き込みの前に size_t 読み取りをスケジュールしていることに気付くでしょう (fp パイプラインのレイテンシを隠すため)。

-fno-strict-aliasing スイッチを設定するか、union または reinterpret_cast を使用して、標準に準拠した方法で値を再解釈できます。

于 2008-09-17T14:48:11.017 に答える
-1

-O3 は「正気」とは見なされません。一部のマルチメディア アプリを除いて、一般的に -O2 が上限しきい値です。

一部のアプリはそこまで行くことさえできず、 -O1 を超えると死んでしまいます。

十分に新しい GCC をお持ちの場合 (ここでは 4.3 を使用しています)、このコマンドをサポートしている可能性があります。

  gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts

注意すれば、おそらくそのリストを調べて、このバグの原因となっている、有効にしている特定の特異な最適化を見つけることができるでしょう。

からman gcc:

  The output is sensitive to the effects of previous command line options, so for example it is possible to find out which
       optimizations are enabled at -O2 by using:

               -O2 --help=optimizers

       Alternatively you can discover which binary optimizations are enabled by -O3 by using:

               gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
               gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
               diff /tmp/O2-opts /tmp/O3-opts | grep enabled
于 2008-09-17T14:47:06.933 に答える
-2

「i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5465)」でコードをテストしました。

問題はありませんでした。出力:

t should be 0 but is 0

したがって、コードにバグはありません。これは、それが優れたコードであるという意味ではありません。しかし、main-function の returntype と "return 0;" を追加します。関数の最後に。

于 2008-09-17T14:52:58.523 に答える