9

質問に答えたのはちょうど20分で、行動がよくわからないという興味深いシナリオを思いつきました。

intPtrが指すサイズnの整数配列を作成します。

int* intPtr;

また、次のような構造体を作成します。

typedef struct {
int val1;
int val2;
//and less or more integer declarations goes on like this(not any other type)
}intStruct;

私の質問は私がキャストをするかどうかですintStruct* structPtr = (intStruct*) intPtr;

構造体の要素をトラバースした場合、すべての要素を正しく取得できますか?アーキテクチャ/コンパイラで位置ずれ(パディングが原因である可能性があります)の可能性はありますか?

4

6 に答える 6

5

この標準は、POD構造体(つまり、最も制限の厳しいクラスの構造体)でさえ、メンバー間にパディングを含めることができるというかなり具体的なものです。(「したがって、POD-structオブジェクト内に名前のないパディングがある可能性がありますが、適切な配置を実現するために必要な場合は、最初はそうではありません。」-非規範的なメモですが、意図は非常に明確です)。

たとえば、標準レイアウト構造体(C ++ 11、§1.8/ 4)の要件を対比します。

自明にコピー可能なタイプまたは標準レイアウトタイプ(3.9)のオブジェクトは、連続したバイトのストレージを占有するものとします。」

...アレイ用のもの(§8.3.4/ 1):

配列型のオブジェクトには、T型のN個のサブオブジェクトの連続して割り当てられた空でないセットが含まれています。

配列では、要素自体を連続して割り当てる必要がありますが、構造体では、ストレージのみを連続して割り当てる必要があります。

「連続ストレージ」要件をより意味のあるものにする可能性のある3番目の可能性は、簡単にコピーできない、または標準のレイアウトではない構造体/クラスを検討することです。この場合、ストレージがまったく隣接していない可能性があります。たとえば、実装では、すべてのプライベート変数を保持するための1つのメモリ領域と、すべてのパブリック変数を保持するための完全に別個のメモリ領域を確保する場合があります。これをもう少し具体的にするために、次のような2つの定義を検討してください。

class A { 
    int a;
public:
    int b;
} a;

class B {
    int x;
public:
    int y;
} b;

これらの定義を使用すると、メモリは次のようにレイアウトされます。

a.a;
b.x;

// ... somewhere else in memory entirely:

a.b;
b.y;

この場合、要素ストレージも隣接している必要はないため、完全に別個の構造体/クラスの部分をインターリーブすることは許可されます。

とはいえ、最初の要素は全体として構造体と同じアドレスにある必要があります(9.2 / 17): "reinterpret_castを使用して適切に変換されたPOD-structオブジェクトへのポインターは、最初のメンバー(またはそのメンバーの場合)を指しますはビットフィールドであり、それが存在するユニットになります)、またはその逆です。」

あなたの場合、POD-structがあるので、(§9.2/ 17): "reinterpret_castを使用して適切に変換されたPOD-structオブジェクトへのポインターは、その最初のメンバーを指します(または、そのメンバーがビットフィールドの場合)。 、次にそれが存在するユニットに)、およびその逆。」最初のメンバー整列され、残りのメンバーはすべて同じタイプであるため、他のメンバー間でパディングが本当に必要になることは不可能です(つまり、ビットフィールドを除いて、構造体に配置できるタイプは、配列。要素の連続的な割り当てが必要です)。単語指向のマシン(初期のDEC Alphaなど)で単語よりも小さい要素がある場合は、パディングによってアクセスがいくらか簡単になる可能性があります。たとえば、初期のDEC Alpha(ハードウェアレベル)は、一度に完全に(64ビット)ワードを読み書きすることしかできませんでした。charそのため、4つの要素の構造体のようなものを考えてみましょう。

struct foo { 
   char a, b, c, d;
};

これらをメモリに配置して連続さfoo::bせる必要がある場合、(たとえば)にアクセスするには、CPUがワードをロードし、それを8ビット右にシフトしてから、マスクしてそのバイトをゼロ拡張して全体を埋める必要があります。登録。

保存はさらに悪化します。CPUは単語全体の現在の値をロードし、その適切な文字サイズの部分の現在の内容をマスクして、新しい値を正しい場所にシフトするか、単語に挿入する必要があります。 、そして最後に結果を保存します。

対照的に、要素間にパディングを使用すると、各要素はシフトやマスキングなどのない単純なロード/ストアになります。

少なくとも、メモリが機能する場合、Alpha用のDECの通常のコンパイラでは、int32ビットであり、long64ビットでした(以前はlong long)。そのため、4intの構造体を使用すると、要素間にさらに32ビットのパディングが表示されると予想できます(最後の要素の後にさらに32ビットも表示されます)。

あなたがPOD構造体を持っていることを考えると、あなたはまだいくつかの可能性を持っています。私がおそらく好むoffsetofのは、構造体のメンバーのオフセットを取得し、それらの配列を作成し、それらのオフセットを介してメンバーにアクセスするために使用することです。以前のいくつかの回答でこれを行う方法を示しました。

于 2012-08-29T15:22:33.000 に答える
3

厳密に言えば、そのようなポインタキャストは許可されておらず、未定義の動作につながります。

ただし、キャストの主な問題は、コンパイラが、最初の要素の前を除いて、構造体内の任意の場所に任意の数のパディングバイトを自由に追加できることです。したがって、それが機能するかどうかは、特定のシステムのアライメント要件、および構造体パディングが有効になっているかどうかによって異なります。

intこれはほとんどの32ビットシステムに当てはまりますが、アドレス可能なデータチャンクの最適サイズと必ずしも同じサイズであるとは限りません。一部の32ビターズはミ​​スアラインメントを気にせず、一部はミスアラインメントを許可しますが、効率の低いコードを生成し、一部はデータをアラインメントする必要があります。理論的には、64ビットの整数(32ビット)の後にパディングを追加して64ビットのチャンクを取得することもできますが、実際には32ビットの命令セットをサポートしています。

このキャストに依存するコードを作成する場合は、次のようなものを追加する必要があります。

static_assert (sizeof(intStruct) == 
               sizeof(int) + sizeof(int));
于 2012-08-27T08:22:36.627 に答える
3

要素タイプが標準レイアウトであることを考えると、合法であることが保証されます。注:以下のすべての参照は、標準に対するものです。

8.3.4配列[dcl.array]

N1-[...]配列型のオブジェクトには、連続して割り当てられた、型のサブオブジェクトの空でないセットが含まれていますT。[...]

タイプのメンバーとstructについては、NT

9.2クラスメンバー[class.mem]

14-同じアクセス制御を持つ(非ユニオン)クラスの非静的データメンバーは、後のメンバーがクラスオブジェクト内でより高いアドレスを持つように割り当てられます。[...]実装の配置要件により、2つの隣接するメンバーが互いにすぐに割り当てられない場合があります[...]
20-標準レイアウトの構造体オブジェクトへのポインターは、を使用して適切に変換さreinterpret_castれ、最初のメンバーを指します[。 ..] およびその逆。[注:したがって、適切な配置を実現するために必要な場合、標準レイアウトの構造体オブジェクト内に名前のないパディングが存在する可能性がありますが、最初は存在しません。—エンドノート]

したがって、問題は、内の位置合わせが必要なパディングstructによって、そのメンバーが相互に連続して割り当てられない可能性があるかどうかです。答えは次のとおりです。

1.8C++オブジェクトモデル[intro.object]

4-[...]自明なコピー可能または標準レイアウトタイプのオブジェクトは、連続したバイトのストレージを占有するものとします。

言い換えると、アイデンティティを尊重しない同じ(標準レイアウト)タイプのstruct a少なくとも2つのメンバーを含むx標準レイアウトは、1.8:4に違反しています。y&a.y == &a.x + 1

アラインメントは、(3.11 Alignment [basic.align]特定のオブジェクトを割り当てることができる連続するアドレス間のバイト数として定義されることに注意してください。したがって、型Tの配置は、の配列内の隣接するオブジェクト間の距離を超えることはできませんT。(5.3.3 Sizeof [expr.sizeof]は、 n個の要素の配列のサイズが要素)は。より大きくすることはできません。したがって、同じタイプの構造体の隣接する要素間に追加のパディングは必要ありません。alignof(T)sizeof(T)アラインメントによるため、9.2:14では認識されません。


AProgrammerのポイントに関して、私は26.4複素数[complex.numbers]の言語を、すべてstd::complex<T>の標準レイアウトタイプの要件。

于 2012-08-29T10:07:56.603 に答える
2

そこでの動作は、ほぼ確実にコンパイラ、アーキテクチャ、およびABIに依存します。ただし、gccを使用している場合は__attribute__((packed))、パディングなしでコンパイラーに構造体メンバーを次々にパックさせるために使用できます。これにより、メモリレイアウトはフラットアレイのレイアウトと一致する必要があります。

于 2012-08-29T11:33:58.417 に答える
1

しばらく前に検索したときに有効であることを保証するものは何も見つかりませんでした。また、C++のstd:: complex <>の場合、より一般的に当てはまる場合はより簡単に定式化できたという明示的な保証を見つけました。だから私は私の検索で何かを逃したのではないかと思います(しかし、証拠の欠如はほとんど欠如の証拠ではなく、その定式化において標準があいまいな場合があります)。

于 2012-08-27T08:24:12.787 に答える
1

C構造体の一般的な配置により、構造体のデータ構造体メンバーがC配列と同じように順番に格納されることが保証されます。したがって、順序が問題になることはありません。

アラインメントに関しては、データ型(int)が1つしかないため、コンパイラーはそうすることができますが、データメンバーをアラインメントするためにパディングを追加する必要があるシナリオはありません。コンパイラーは、構造体の先頭の前にパディングを追加できますが、データ構造の先頭にパディングを追加することはできません。したがって、コンパイラが状況にパディングを追加する場合は、

これの代わりに:[4Byte int] [4Byte int] [4Byte int] ... [4Byte int]

データ構造は次のように保存する必要があります:
[4バイトデータ] [4バイトパディング] [4バイトデータ]...これは不合理です。

全体的に、このキャストはあなたの状況で問題なく動作するはずだと思いますが、それを使用するのは悪い習慣だと思います。

于 2012-08-30T21:22:08.030 に答える