Type_punning
「型のパンニング」
でコンピュータサイエンス、型パンニングも任意のプログラミング技術のための共通の用語覆すまたは回避することである型システムのプログラミング言語形式言語の境界内に達成することは困難または不可能であろう効果を達成するためです。
でCおよびC ++、このような構成ポインタ 型変換およびunion- C ++は、加算基準型変換をし、reinterpret_castこのリストに-いくつかの種類は、実際に標準言語でサポートされていないが、タイプpunning多くの種類のを可能にするために設けられています。
でパスカルプログラミング言語を使用するレコードを有する変異体は、複数の方法で特定のデータ型を治療するために使用され得る、又は方法で通常許されません。
コンテンツ
1 ソケットの例
2 浮動小数点の例
3 言語別
3.1 CおよびC ++
3.1.1 ポインタの使用
3.1.2 共用体の使用
3.2 パスカル 3.3 NS#
3.3.1 ポインタ
3.3.2 構造体ユニオン
3.3.3 生のCILコード
4 参考文献
5 外部リンク
ソケットの例
型のパンニングの典型的な例の1つは、バークレーソケットインターフェイスに開いているが初期化されていないソケットをIPアドレスにバインドする関数は、次のように宣言されています。
int bind (int sockfd 、struct sockaddr * my_addr 、socklen_t addrlen );
このbind関数は通常、次のように呼び出されます。
struct sockaddr_in sa = { 0 }; int sockfd = …; sa 。sin_family = AF_INET ; sa 。sin_port = htons (ポート); bind (sockfd 、(struct sockaddr * )&sa 、sizeof sa );
Berkeleyソケットライブラリは基本的に、Cではstruct sockaddr_inへのポインタがstruct sockaddr;へのポインタに自由に変換できるという事実に依存しています。さらに、2つの構造タイプが同じメモリレイアウトを共有していること。したがって、構造体のフィールドへの参照my_addr->sin_family(my_addrタイプであるがstruct sockaddr*)は、実際のフィールドを参照するsa.sin_family(saタイプのものですstruct sockaddr_in)。言い換えると、ソケットライブラリは、型のパンニングを使用して、基本的な形式のポリモーフィズムまたは継承を実装します。
プログラミングの世界でよく見られるのは、「パディングされた」データ構造を使用して、事実上同じストレージスペースにさまざまな種類の値を格納できるようにすることです。これは、最適化のために2つの構造が相互排他的に使用される場合によく見られます。
浮動小数点の例
前の例のように、型のパンニングのすべての例に構造が含まれているわけではありません。浮動小数点数が負であるかどうかを判別したいとします。私たちは書くことができます:
bool is_negative (float x ){
x < 0.0を返す; }
ただし、浮動小数点の比較に費用がかかり、それfloatがIEEE浮動小数点標準に従って表され、整数が32ビット幅であると仮定すると、型のパンニングを実行して浮動小数点数の符号ビットを抽出できます。整数演算のみを使用:
bool is_negative (float x ){
unsigned int * ui = (unsigned int * )&x ;
return * ui &0x80000000 ; }
特別な場合に:行動が正確に同じではないことに注意してくださいxされ、負のゼロ、最初の実装歩留まりfalse第収率つつtrue。また、最初の実装はfalse任意のNaN値に対して返されますが、後者trueは符号ビットが設定されたNaN値に対して返される場合が
この種のパンニングは、ほとんどの場合よりも危険です。前者の例は、構造体のレイアウトとポインターの変換可能性に関するCプログラミング言語による保証のみに依存していましたが、後者の例は、特定のシステムのハードウェアに関する仮定に依存しています。コンパイラが最適化に失敗するタイムクリティカルなコードなどの一部の状況では、危険なコードが必要になる場合がこのような場合、そのようなすべての仮定をコメントに文書化し、静的アサーションを導入して移植性の期待を検証することで、コードを保守しやすくすることができます。
浮動小数点しゃれの実際的な例には、Quake IIIで普及している高速逆平方根、整数としての高速FP比較、整数としてインクリメントすることによる隣接値の検索(実装)が含まれます。nextafter
言語別
CおよびC ++
浮動小数点数のビット表現についての仮定に加えて、浮動小数点の上方タイプpunning例では、オブジェクトがアクセスされる方法のC言語の制約に違反:の宣言された型がxあるfloatが、それを介して読み出されますタイプの式unsigned int。多くの一般的なプラットフォームでは、このポインターのしゃれの使用は、異なるポインターがマシン固有の方法で整列されている場合に問題を引き起こす可能性がさらに、異なるサイズのポインタは、同じメモリへのアクセスをエイリアスする可能性があり、コンパイラによってチェックされない問題を引き起こします。
ポインタの使用
型のパンニングの素朴な試みは、ポインターを使用することで実現できます。
float pi = 3.14159 ; uint32_t piAsRawData = * (uint32_t * )&pi ;
C標準によれば、このコードはコンパイルすべきではありません(または、コンパイルする必要はありません)。ただし、コンパイルする場合は、piAsRawData通常、piの生のビットが含まれます。
の使用 union
を使用して型のパンニングを修正しようとするのはよくある間違いunionです。(次の例では、浮動小数点型のIEEE-754ビット表現を追加で想定しています)。
bool is_negative (float x ){
ユニオン {
unsigned int ui ;
フロートd ; } my_union = { 。d = x };
my_unionを返します。ui &0x80000000 ; }
my_union.ui他のメンバーを初期化した後のアクセスmy_union.dは、Cでは型のパンニングの形式であり、結果は未指定の動作(およびC ++では未定義の動作)になります。
§6.5/ 7 の文言は、代替の組合員を読むことが許されることを意味するように誤解される可能性がただし、テキストには「オブジェクトは、保存された値に…だけがアクセスできるようにする必要があります」と書かれています。これは限定的な表現であり、最後に保存されたメンバーに関係なく、すべての可能なユニオンメンバーにアクセスできるというステートメントではありません。したがって、unionavoidsを使用すると、ポインタを直接しゃれするだけの問題を回避できます。
コンパイラが型のパンニングをサポートしていない場合、コンパイラが警告やエラーを報告する可能性が低くなるため、ポインタを使用した型のパンニングよりも安全性が低いと見なされることも
GCCのようなコンパイラは、言語拡張として上記の例のようなエイリアス可能な値アクセスをサポートします。このような拡張機能のないコンパイラでは、厳密なエイリアスルールは、明示的なmemcpyによって、または「仲介者」としてcharポインタを使用することによってのみ破られます(これらは自由にエイリアスできるため)。
型のパンニングの別の例については、配列のストライドを参照して
パスカル
バリアントレコードを使用すると、参照されているバリアントに応じて、データ型を複数の種類のデータとして扱うことができます。次の例では、整数は16ビットであると推定され、longintとrealは32であると推定され、文字は8ビットであると推定されます。
タイプ VariantRecord = レコード
ケース RecType : LongInt of
1 : (I : array of Integer ); (*ここには表示されません:バリアントレコードのcaseステートメントに複数の変数が存在する可能性があります*)
2 : (L : LongInt ); 3 : (R : 実数 ); 4 : (C : 配列 の チャー )。
終了;var V : VariantRecord ; K : 整数; LA : LongInt ; RA : リアル; Ch : キャラクター;V 。I := 1 ; CH
:= V 。C ; (*これはVIの最初のバイトを抽出します*)V 。R := 8.3 ; LA
:= V 。L ;(*これはRealを整数に格納します*)
Pascalでは、実数を整数にコピーすると、それが切り捨てられた値に変換されます。このメソッドは、浮動小数点数の2進値を長整数(32ビット)として変換します。これは同じではなく、一部のシステムでは長整数値と互換性がない場合が
これらの例は、奇妙な変換を作成するために使用できますが、場合によっては、特定のデータの場所を決定するためなど、これらのタイプの構成の正当な使用法がある場合が次の例では、ポインタとlongintの両方が32ビットであると想定されています。
タイプ PA = ^ Arec ; Arec = レコード
ケース RT : LongInt of
1 : (P : PA);
2 : (L : LongInt );
終了;var PP : PA ; K : LongInt ;新規(PP ); PP ^。P := PP ; WriteLn (’変数PPはアドレス’ 、 Hex (PP ^ 。L ))にあります;
ここで、「new」はポインタにメモリを割り当てるためのPascalの標準ルーチンであり、「hex」はおそらく整数の値を説明する16進文字列を出力するためのルーチンです。これにより、通常は許可されていないポインタのアドレスを表示できるようになります。(ポインターは読み取りまたは書き込みできず、割り当てられるだけです。)ポインターの整数バリアントに値を割り当てると、システムメモリ内の任意の場所を調べたり書き込んだりできるようになります。
PP ^。L := 0 ; PP := PP ^。P ; (* PPはアドレス0を指すようになりました*)K
:= PP ^。L ; (* Kにはワード0の値が含まれます*)WriteLn (’このマシンのワード0には’ 、 Kが含まれます);
この構成により、プログラムが実行されているマシンまたはプログラムが実行されているオペレーティングシステムでアドレス0が読み取られないように保護されている場合、プログラムチェックまたは保護違反が発生する可能性が
C / C ++のキャスト手法の再解釈はPascalでも機能します。これは、たとえば次の場合に役立ちます。バイトストリームからdwordを読み取り、それらをfloatとして扱いたい。これが実際の例で、dwordをfloatに再解釈キャストします。
タイプ pReal = ^ Real ;var DW : DWord ; F : リアル;F := pReal (@ DW )^;
NS#
C# (およびその他の.NET言語)、タイプのパンニングもあるため、型システムの実現には少し難しいですが、ポインタや構造体組合を使用して、それにもかかわらず行うことができます。
ポインタ
C#では、いわゆるネイティブ型、つまりstring、他のネイティブ型のみで構成されるプリミティブ型(を除く)、列挙型、配列、または構造体へのポインターのみが許可されます。ポインタは、「安全でない」とマークされたコードブロックでのみ許可されることに注意して
float pi = 3.14159 ; uint piAsRawData = *(uint *)&pi ;
構造体ユニオン
構造体の共用体は、「安全でない」コードの概念なしで許可されますが、新しい型の定義が必要です。
[StructLayout(LayoutKind.Explicit)] struct FloatAndUIntUnion { [FieldOffset(0)] public float DataAsFloat ; [FieldOffset(0)] public uint DataAsUInt ; }//..。FloatAndUIntUnion ユニオン; ユニオン。DataAsFloat = 3.14159 ; uint piAsRawData = union 。DataAsUInt ;
生のCILコード
生CILは、それはタイプの制限のほとんどを持っていないため、C#の代わりに使用することができます。これにより、たとえば、ジェネリック型の2つの列挙値を組み合わせることができます。
TEnum a = …; TEnum b = …; TEnumの 組み合わせ = a | b ; // 違法
これは、次のCILコードによって回避できます。
。メソッド パブリック 静的の hidebysig !! TEnum CombineEnums < valuetype 。CTOR (システム。ValueTypeに) TEnum >( !! TEnum A 、
!! TEnum B ) CIL 管理{ 。maxstack 2 ldarg 。0
ldarg 。1 または // aとbは同じタイプであり、したがって同じサイズであるため、これによってオーバーフローが発生することはありません。 ret }
cpblkCILオペコードは、バイト配列に構造体を変換するなどのいくつかの他のトリックを可能にします。
。方法 パブリック 静的 hidebysig UINT8 [] ToByteArray <値型 。CTOR (システム。ValueTypeに) T >( !! T & V C#の’T REF’ // ) CIL 管理{ 。ローカル init ( uint8 [] ) 。maxstack 3 //長さがsizeof(T)の新しいバイト配列を作成し、ローカルの 0sizeofに 格納します!! T newarr uint8 dup
//後で(1) stlocのためにスタックにコピーを保持します。0 ldc 。i4 。0 ldelema uint8 // memcpy(local 0、&v、sizeof(T)); // <配列はまだスタック上にあります 。(1)> ldargを参照して0 //これは ‘v’の*アドレス*です。これは、そのタイプが ‘!! T&’ sizeof !!であるためです。T cpblk ldloc 。0 RET }
参考文献
^ Herf、Michael。「基数トリック」。立体視:グラフィックス。
^ 「愚かなフロートトリック」。ランダムASCII-ブルースドーソンの技術ブログ。2012年1月24日。
^ ISO / IEC 9899:1999 s6.5 / 7
^ 「§6.5.2.3 / 3、脚注97」、ISO / IEC 9899:2018(PDF)、2018、p。59、2018-12-30にオリジナル(PDF)からアーカイブ、ユニオンオブジェクトのコンテンツを読み取るために使用されたメンバーが、オブジェクトに値を格納するために最後に使用されたメンバーと同じでない場合、値のオブジェクト表現は、6.2.6で説明されているように、新しいタイプのオブジェクト表現として再解釈されます(「型のパンニング」と呼ばれることもあるプロセス)。これはトラップ表現である可能性が
^ “§J.1/ 1、箇条書き11″、ISO / IEC 9899:2018(PDF)、2018、p。403、2018-12-30にオリジナル(PDF)からアーカイブ、以下は指定されていません:…最後に格納されたもの以外のユニオンメンバーに対応するバイトの値(6.2.6.1)。
^ ISO / IEC 14882:2011セクション9.5
^ GCC:非バグ
外部リンク
セクションのGCCの上のマニュアル-fstrictエイリアシングいくつかのタイプpunningを破り、
欠陥レポート257にC99の標準、偶然の点で「タイプpunning」を定義unionし、上記最後の例の実装定義の動作を取り巻く問題を議論
型のパンニングのためのユニオンの使用に関する欠陥レポート283