64

Stack Overflowの質問に対するいくつかの回答floatのIEEE単精度ビットを取得するには、型のパンニングの構造を使用することをお勧めしますunion(例:aのビットをafloatに変換するuint32_t)。

union {
    float f;
    uint32_t u;
} un;
un.f = your_float;
uint32_t target = un.u;

ただし、uint32_t組合員の価値は、C99標準(少なくともドラフトn1124)に従って指定されていないようです。ここで、セクション6.2.6.1.7には次のように記載されています。

共用体型のオブジェクトのメンバーに値が格納されている場合、そのメンバーに対応していないが他のメンバーに対応しているオブジェクト表現のバイトは、指定されていない値を取ります。

C11 n1570ドラフトの少なくとも1つの脚注は、これがもはや当てはまらないことを示唆しているようです(6.5.2.3の脚注95を参照)。

共用体オブジェクトの内容を読み取るために使用されたメンバーが、オブジェクトに値を格納するために最後に使用されたメンバーと同じでない場合、値のオブジェクト表現の適切な部分は、新しいタイプのオブジェクト表現として再解釈されます。 6.2.6で説明されています(「型のパンニング」と呼ばれることもあるプロセス)。これはトラップ表現である可能性があります。

ただし、セクション6.2.6.1.7のテキストは、C99ドラフトでもC11ドラフトでも同じです。

この動作は実際にはC99では指定されていませんか?C11で指定されていますか?ほとんどのコンパイラがこれをサポートしているように見えますが、それが標準で指定されているのか、それとも非常に一般的な拡張機能であるのかを知っておくと便利です。

4

4 に答える 4

43

共用体を使用した型のパンニングの動作がC89からC99に変更されました。C99での動作はC11と同じです。

Wugが彼の回答で述べたように、型のパンニングはC99/C11で許可されています。ユニオンメンバーのサイズが異なる場合、トラップとなる可能性のある不特定の値が読み取られます。

脚注は、Clive DW Feather Defect Report#257の後にC99で追加されました:

最後に、C90からC99への変更の1つは、最後のストアが別のストアにある場合に、ユニオンの1つのメンバーにアクセスする際の制限を削除することでした。その場合、動作は値の表現に依存するというのが理論的根拠でした。この点は誤解されることが多いので、規格で明確にする価値があるかもしれません。

[...]

「型のパンニング」に関する問題に対処するには、6.5.2.3#3の「namedmember」という単語に新しい脚注78aを添付します。78aユニオンオブジェクトのコンテンツにアクセスするために使用されたメンバーが最後のメンバーと同じでない場合オブジェクトに値を格納するために使用され、値のオブジェクト表現の適切な部分は、6.2.6で説明されているように、新しいタイプのオブジェクト表現として再解釈されます(「型のパンニング」と呼ばれることもあるプロセス)。これはトラップ表現である可能性があります。

Clive DW Featherの文言は、C欠陥レポート#283の回答で、技術正誤表として受け入れられました。

于 2012-07-24T23:09:21.527 に答える
19

元のC99仕様では、これは指定されていませんでした。

C99の技術的正誤表の1つ(TR2、私は思う)は、この見落としを修正するために脚注82を追加しました。

共用体オブジェクトのコンテンツにアクセスするために使用されるメンバーが、オブジェクトに値を格納するために最後に使用されるメンバーと同じでない場合、値のオブジェクト表現の適切な部分は、新しいタイプのオブジェクト表現として再解釈されます。 6.2.6で説明されています(「型のパンニング」と呼ばれることもあるプロセス)。これはトラップ表現である可能性があります。

その脚注はC11標準に保持されています(C11の脚注95です)。

于 2012-07-24T22:46:16.420 に答える
9

これは常に「難しい」ものでした。他の人が指摘しているように、脚注は技術コレジェンダムを介してC99に追加されました。それは次のように読みます:

共用体オブジェクトのコンテンツにアクセスするために使用されるメンバーが、オブジェクトに値を格納するために最後に使用されるメンバーと同じでない場合、値のオブジェクト表現の適切な部分は、新しいタイプのオブジェクト表現として再解釈されます。 6.2.6で説明されています(「型のパンニング」と呼ばれることもあるプロセス)。これはトラップ表現である可能性があります。

ただし、脚注は序文で非規範的として指定されています。

付録DおよびFは、この標準の規範的な部分を形成します。付録A、B、C、E、G、H、I、J、参​​考文献、および索引は情報提供のみを目的としています。ISO / IEC指令のパート3に従い、この序文、序文、注記、脚注、および例も情報提供のみを目的としています

つまり、脚注は行動を禁止することはできません。既存のテキストのみを明確にする必要があります。これは人気のない意見ですが、上で引用した脚注は実際にはこの点で失敗しています-規範的なテキストで禁止されているそのような行動はありません。実際、6.7.2.1などの矛盾するセクションがあります。

...最大で1つのメンバーの値は、いつでもユニオンオブジェクトに格納できます

6.5.2.3と組み合わせて(「。」演算子を使用したユニオンメンバーへのアクセスに関して):

値は指定されたメンバーの値です

つまり、1つのメンバーの値のみを格納できる場合、別のメンバーの値は存在しません。これは、ユニオンを介した型のパンニングが不可能であることを強く意味します。メンバーアクセスにより、存在しない値が生成されます。同じテキストがC11ドキュメントにまだ存在します。

ただし、脚注を追加する目的が型のパンニングを可能にすることであったことは明らかです。委員会が、規範的なテキストを含まない脚注の規則を破ったように見えるだけです。脚注を受け入れるには、脚注が規範的ではないというセクションを実際に無視するか、脚注の結論をサポートするような方法で規範的なテキストを解釈する方法を理解する必要があります(私が試しましたが、失敗しました)。

脚注を批准するために私たちができる最善のことは、6.2.5からの「重複するオブジェクト」のセットとしてのユニオンの定義についていくつかの仮定をすることです。

共用体型は、重複する空でないメンバーオブジェクトのセットを記述します。各オブジェクトには、オプションで指定された名前があり、場合によっては個別の型があります。

残念ながら、「オーバーラップ」が何を意味するのかについての詳細はありません。オブジェクトは、(3.14)「実行環境のデータストレージの領域。その内容は値を表すことができます」として定義されます(同じストレージ領域が2つ以上の別個のオブジェクトによって識別できることは、「重複するオブジェクト」によって示されます。 「上記の定義、つまり、オブジェクトはストレージ領域とは別のIDを持っています)。合理的な仮定は、(特定のユニオンインスタンスの)ユニオンメンバーが同じストレージ領域を使用することであるように思われます。

6.7.2.1 / 6.5.2.3を無視し、脚注が示すように、任意のユニオンメンバーを読み取ると、対応するストレージ領域のコンテンツによって表される値が返されることを許可したとしても、これにより型のパンニングが可能になります。 -6.5の問題のあるstrict-aliasingルールは、タイプ以外のオブジェクトへのアクセスを(特定のマイナーな例外を除いて)禁止します。「アクセス」は(3.1)「オブジェクトの値を読み取るまたは変更するための<実行時アクション>」であり、重複するオブジェクトのセットの1つを変更すると、必然的に他のオブジェクトも変更されるため、厳密なエイリアシングルールはユニオンメンバーに書き込むことによって違反される可能性があります(その後、別のメンバーを介して読み取られるかどうかに関係なく)。

たとえば、規格の文言では、次のことは違法です。

union {
   int a;
   float b;
} u;

u.a = 0; // modifies a float object by an lvalue of type int
int *pa = &u.a;
*pa = 1; // also modifies a float object, without union lvalue involved

(具体的には、コメント付きの2行は厳密なエイリアシングルールに違反しています)。

厳密に言えば、脚注は別の問題、つまり非アクティブな組合員を読むことについて語っています。ただし、上記の他のセクションと組み合わせた厳密なエイリアシングルールは、その適用性を大幅に制限し、特に、一般に型のパンニングを許可しないことを意味します(ただし、タイプの特定の組み合わせに対してのみ)。

苛立たしいことに、規格の開発を担当する委員会は、型のパンニングが一般に組合を通じて可能になることを意図しているようですが、規格のテキストがまだそれを許可していないことに問題はないようです。

注目に値するのは、(コンパイラベンダーによる)コンセンサスの理解は、共用体を介した型のパンニングが許可されているように見えることですが、「アクセスは共用体型を介して行う必要があります」(たとえば、上記の例の最初のコメント行であり、2番目の行ではない) )。これが読み取りアクセスと書き込みアクセスの両方に適用されるかどうかは少し不明確であり、標準のテキストではサポートされていません(脚注は無視してください)。

結論:共用体を介した型のパンニングは合法であると広く認められていますが(ほとんどの場合、アクセスが「共用体型を介して」行われる場合にのみ許可されると考えられます)、標準の文言は、特定の部分を除いてすべてを禁止しています。些細なケース。

あなたが引用するセクション:

共用体型のオブジェクトのメンバーに値が格納されている場合、そのメンバーに対応していないが他のメンバーに対応しているオブジェクト表現のバイトは、指定されていない値を取ります。

...ただし、注意深く読む必要があります。「そのメンバーに対応しないオブジェクト表現のバイト」は、メンバーのサイズを超えるバイトを参照しています。これは、型のパンニングの問題ではありません(ただし、ユニオンメンバーへの書き込みが大きなメンバーの「余分な」部分はそのままです)。

于 2016-04-18T22:48:34.857 に答える
0

ただし、これはC99標準(少なくともドラフトn1124)に違反しているように見えます。ここでは、セクション6.2.6.1.7にいくつかの事項が記載されています。この動作は実際にはC99では指定されていませんか?

いいえ、大丈夫です。

共用体型のオブジェクトのメンバーに値が格納されている場合、そのメンバーに対応していないが他のメンバーに対応しているオブジェクト表現のバイトは、指定されていない値を取ります。

これは、さまざまなサイズのデータ​​ブロックに適用されます。つまり、あなたが持っている場合:

union u
{
    float f;
    double d;
};

fに何かを割り当てると、dの下位4バイトが変更されますが、上位4バイトは不確定な状態になります。

ユニオンは主に型のパンニングのために存在します。

于 2012-07-24T22:02:25.347 に答える