単純な構造 (POD) を考えてみましょう。
struct xyz
{
float x, y, z;
};
次のコードは問題ないと思いますか? 隙間はないと考えていいですか?基準は何と言っていますか?それは POD にも当てはまりますか? それはクラスに当てはまりますか?
xyz v;
float* p = &v.x;
p[0] = 1.0f;
p[1] = 2.0f; // Is it ok?
p[2] = 3.0f; // Is it ok?
ここでの答えは少しトリッキーです。C++ 標準では、POD データ型には C レイアウトの互換性が保証されると記載されています (参照)。C 仕様のセクション9.2によると、次の場合、構造体のメンバーは順番に配置されます。
はい、このソリューションは、型が現在のプラットフォームで互換性のあるアラインメントを持っている限り機能しfloat
ます (プラットフォームのワードサイズです)。したがって、これは 32 ビット プロセッサでは機能するはずですが、64 ビット プロセッサでは失敗すると思います。本質的sizeof(void*)
に異なる場所ならどこでもsizeof(float)
これは標準で保証されておらず、多くのシステムでは機能しません。理由は次のとおりです。
これは、p[1]
が と同じ場所にxyz.y
あるか、部分的に重複しているか、まったく重複していない可能性があることを意味します。
いいえ、最初のフィールド以外はOKではありません。
C++ 標準から:
9.2 クラスメンバー
reinterpret_cast を使用して適切に変換された POD 構造体オブジェクトへのポインターは、その最初のメンバー (または、そのメンバーがビットフィールドの場合は、それが存在するユニット) を指し、その逆も同様です。[注: したがって、POD 構造体オブジェクト内に名前のないパディングがある場合がありますが、適切な配置を実現するために必要に応じて、先頭にはありません。
疑わしい場合は、アプリケーションに合わせてデータ構造を変更してください。
struct xyz
{
float p[3];
};
読みやすくするために、次のことを検討してください。
struct xyz
{
enum { x_index = 0, y_index, z_index, MAX_FLOATS};
float p[MAX_FLOATS];
float X(void) const {return p[x_index];}
float X(const float& new_x) {p[x_index] = new_x;}
float Y(void) const {return p[y_index];}
float Y(const float& new_y) {p[y_index] = new_y;}
float Z(void) const {return p[z_index];}
float Z(const float& new_z) {p[z_index] = new_z;}
};
おそらく、さらにカプセル化を追加することもできます。
struct Functor
{
virtual void operator()(const float& f) = 0;
};
struct xyz
{
void for_each(Functor& ftor)
{
ftor(p[0]);
ftor(p[1]);
ftor(p[2]);
return;
}
private:
float p[3];
}
一般に、データ構造を2つ以上の異なる方法で処理する必要がある場合は、おそらくデータ構造を再設計する必要があります。またはコード。
ハードウェアに依存します。標準では、POD クラスに未指定の予測不可能なパディングを明示的に許可しています。これは C++ ウィキペディアのページに記載されており、脚注に仕様のリファレンスが記載されています。
^ ab ISO/IEC (2003). ISO/IEC 14882:2003(E): プログラミング言語 - C++ §9.2 クラス メンバー [class.mem] パラ。17
ただし、実際には、一般的なハードウェアとコンパイラでは問題ありません。
Doom III のソース コードを見てみましょう。
class idVec4 {
public:
float x;
float y;
float z;
float w;
...
const float * ToFloatPtr( void ) const;
float * ToFloatPtr( void );
...
}
ID_INLINE const float *idVec4::ToFloatPtr( void ) const {
return &x;
}
ID_INLINE float *idVec4::ToFloatPtr( void ) {
return &x;
}
多くのシステムで動作します。
いいえ、ギャップがないと仮定することはできません。アーキテクチャを確認して、移植性を気にしない場合は問題ありません。
しかし、32 ビット float を使用した 64 ビット アーキテクチャを想像してみてください。コンパイラは、構造体の float を 64 ビット境界に揃えることができます。
p[1]
あなたにがらくたを与えるでしょう、そして
p[2]
あなたが何から得たと思うかをあなたに与えるでしょう
p[1]
&c。
ただし、コンパイラは、構造をパックする何らかの方法を提供する場合があります。それでも「標準」ではありません---標準はそのようなことを提供しておらず、さまざまなコンパイラーはこれを行うための非常に互換性のない方法を提供しています---しかし、移植性が高くなる可能性があります。
標準では、メモリ内の配置の順序が定義の順序と一致する必要がありますが、それらの間の任意のパディングが許可されています。メンバー間にアクセス指定子 ( public:
、private:
またはprotected:
) があると、順序の保証さえなくなります。
編集: 3 つのメンバーすべてが同じプリミティブ型 (つまり、それ自体が構造体などではない) であるという特定のケースでは、かなりの可能性があります。プリミティブ型の場合、オブジェクトのサイズと配置の要件は多くの場合同じです。それはうまくいきます。
OTOH、これは偶然に過ぎず、強みというより弱みになりがちです。コードが間違っているため、理想的には、最も重要な顧客となる会社の所有者にデモを提供する日まで、動作しているように見えるのではなく、すぐに失敗します (もちろん)可能な限り最も凶悪な方法で失敗します...
他の人が指摘しているように、アライメントは仕様によって保証されていません。多くの人はハードウェアに依存していると言っていますが、実際にはコンパイラにも依存しています。ハードウェアは、さまざまな形式をサポートしている場合があります。PPC コンパイラは、データを「パック」する方法のプラグマをサポートしていることを覚えています。「ネイティブ」境界でパックするか、32 ビット境界などに強制することができます。
あなたがやろうとしていることを理解するのは良いことです。入力データを「解析」しようとしている場合は、実際のパーサーを使用することをお勧めします。シリアライズする場合は、実際のシリアライザを作成してください。ドライバーなどのビットをいじろうとしている場合、デバイス仕様は、書き込み先の特定のメモリ マップを提供する必要があります。次に、POD 構造を記述し、正しいアライメント プラグマ (サポートされている場合) を指定して、先に進みます。
コードは問題ありません (同じ環境で生成されたデータのみを処理する限り)。POD の場合、構造体は宣言どおりにメモリに配置されます。ただし、一般に、注意が必要な落とし穴があります。コンパイラは、各メンバーのアライメント要件が確実に守られるように、構造体にパディングを挿入します。
あなたの例があったか
struct xyz
{
float x;
bool y;
float z;
};
その場合、 z は構造体の 8 バイトから始まり、 sizeof(xyz) は 12 になります。これは、 float
s が (通常) 4 バイトでアラインされているためです。
同様に、
struct xyz
{
float x;
bool y;
};
sizeof(xyz) == 8、((xyz*)ptr)+1 が x のアラインメント要件に従うポインターを返すようにします。
アラインメント要件/型サイズはコンパイラ/プラットフォーム間で異なる可能性があるため、そのようなコードは一般に移植可能ではありません。
#pragma pack
MSVC など) http://msdn.microsoft.com/en-us/library/aa273913%28v=vs.60%29.aspx__declspec(align(
MSVC など) http://msdn.microsoft.com/en-us/library/83ythb65.aspxあなたの思い込みを崩す可能性のある 2 つの要因です。float は通常 4 バイト幅であるため、このような大きな変数の位置合わせがずれることはめったにありません。しかし、コードを壊すのは簡単です。
この問題は、ショート (BMP や TGA など) を含むバイナリ読み取りヘッダー構造体の場合に最も顕著です。忘れるpack 1
と災害が発生します。
メンバー (.x、.y、および .z) としてアクセスされる座標を保持する構造体が必要であると仮定しますが、それらにアクセスする必要があるとします。たとえば、OpenGL の方法 (配列であるかのように) です。
配列としてアクセスできるように、構造体の [] 演算子を実装してみることができます。何かのようなもの:
struct xyz
{
float x, y, z;
float& operator[] (unsigned int i)
{
switch (i)
{
case 0:
return x;
break;
case 1:
return y;
break;
case 2:
return z;
break;
default:
throw std::exception
break;
}
}
};