12

私は現在、小さなコンパイラを構築するプロジェクトに取り組んでいます。

私は非常に単純な仮想マシンをターゲットに構築するというアプローチをとることに決めたので、elf や intel アセンブリなどの詳細を学ぶことについて心配する必要はありません。

私の質問は、共用体を使用した C での型のしゃれについてです。VM のメモリでは 32 ビット整数と 32 ビット浮動小数点値のみをサポートすることにしました。これを容易にするために、vm の「メイン メモリ」は次のように設定されます。

typedef union
{    
    int i;
    float f;
}word;


memory = (word *)malloc(mem_size * sizeof(word));

そのため、メモリ セクションを命令に応じて int または float として扱うことができます。

これは技術的にタイプのパニングですか?int をメモリの単語として使用し、float* を使用してそれらを float のように扱うとしたら、確かにそうなるでしょう。私の現在のアプローチは、構文的には異なりますが、意味的には異なるとは思いません。結局、私はまだメモリ内の 32 ビットを int または float として扱っています。

私がオンラインで思いついた唯一の情報は、これが実装に依存していることを示唆しています。大量のスペースを無駄にすることなく、これを達成するためのよりポータブルな方法はありますか?

私は次のことを行うことができましたが、その場合、共用体に関して 2 倍以上のメモリを消費し、「車輪の再発明」を行うことになります。

typedef struct
{
    int i;
    float f;
    char is_int;
}

編集

私はおそらく私の正確な質問を明確にしませんでした。未定義の動作なしで、共用体から float または int のいずれかを使用できることを認識しています。私が求めているのは、具体的には、最後に設定された値が何であるかを知らなくても、int または float として安全に使用できる 32 ビットのメモリ位置を持つ方法です。他のタイプが使用されている状況を説明したいと思います。

4

2 に答える 2

14

はい、ユニオンの1つのメンバーを保存して別のメンバーを読み取ることは、型のパニングです(型が十分に異なると仮定します)。さらに、これは、C 言語で公式にサポートされている唯一のユニバーサル (任意の型から任意の型へ) 型のしゃれです。この場合、型パニングが実際に発生すること、つまり、ある型のオブジェクトを別の型のオブジェクトとして読み取ろうとする物理的な試みが行われることを言語が約束しているという意味でサポートされています。とりわけ、共用体の 1 つのメンバーを書き込み、別のメンバーを読み取ると、書き込みと読み取りの間のデータ依存関係が暗示されることを意味します。ただし、これでも、型のしゃれによってトラップ表現が生成されないようにするという負担が残ります。

キャストされたポインターを型のパニング (通常は「古典的な」型のパニングとして理解されている) に使用する場合、言語は、一般的なケースでは動作が未定義であることを明示的に示します (オブジェクトの値をchars の配列として再解釈することや、その他の制限されたケースは別として)。GCC のようなコンパイラは、いわゆる「厳密なエイリアシング セマンティクス」を実装しています。これは基本的に、ポインタ ベースの型パニングが期待どおりに機能しない可能性があることを意味します。たとえば、コンパイラは、タイプパニングされた読み取りと書き込みの間のデータ依存関係を無視し、それらを任意に再配置して、意図を完全に台無しにする可能性があります。これ

int i;
float f;

i = 5;
f = *(float *) &i;

実際に簡単に再配置できます

f = *(float *) &i;
i = 5;

具体的には、厳密にエイリアスされたコンパイラが、例の書き込みと読み取りの間のデータ依存性の可能性を意図的に無視するためです。

最新の C コンパイラでは、あるオブジェクトの値を別の型の値として物理的に再解釈する必要がある場合、memcpyあるオブジェクトから別のオブジェクトにバイトを変換するか、共用体ベースの型パニングに制限されます。他に方法はありません。ポインターのキャストは、もはや実行可能なオプションではありません。

于 2012-07-11T23:33:43.367 に答える
7

最近保存されたメンバー(intまたは)にのみアクセスする限り、問題はなく、実際の実装の依存関係もありません。floatユニオンメンバーに値を格納してから同じメンバーを読み取ることは、完全に安全で明確に定義されています。

(私が見たすべてのシステムにありますが、intとが同じサイズであるという保証はありません。)float

一方のメンバーに値を格納してからもう一方のメンバーを読み取る場合、それは型のパンニングです。最新のC11ドラフトの脚注を引用します。

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

于 2012-07-11T22:58:57.947 に答える