• ベストアンサー

アラインメントに厳格なCPU対策

アラインメントについてそろそろ詳しく知りたくなりました。 C++です。 1.以下のalignof(type)でアラインメントは求まることが保証されますか? template<class T> class alignmentof__{ struct S { char c; T t; }; public: enum { value = offsetof(S, t) }; }; #define alignof(type) (alignmentof__<type>::value) 2.また以下のコードで #define SYSTEM_ALIGN 8 template<class T> struct A_l_i_{ enum { Small = alignof(T) <= SYSTEM_ALIGN }; }; template<class T, bool B> struct Align{ template <class U> static T* address(U* p){ return (T*)(UINT(p+alignof(T)-1)&~(alignof(T)-1)); } }; template<class T> struct Align<T,true>{ template <class U> static T* address(U*p){ return (T*)p; } }; #define ALISS(C,P) (Align<C,A_l_i_<C>::Small>::address(P)) これで、alignof(T)が8以下なら下の特殊化がなされ、8より大きければ上の通常のAlignが展開されます。 そしてaddress関数には別に適当に確保して、アラインメントが class T 用に調整されていないバッファの先頭アドレスを渡し、調整後のアドレスを返却する、という意図があるのですが これは本当にちゃんとそういう動作になっているのでしょうか? 3.やってみたら私の環境ではデストラクタが10回呼ばれ、正常に処理を抜けることができたのですが 上記を使い以下のように配置newを用いることは本当に可能ですか? (保証されているのでしょうか?) class BBB { double d[8]; public: ~BBB(){static int i=0; if (++i==10) printf("デストラクタが10回呼ばれました"); } }; char c[ sizeof(BBB)*10+alignof(BBB)-SYSTEM_ALIGN ]; BBB* b = new( ALISS(BBB,c) ) BBB[10]; for (int i=10;i--;) { printf( "%d\n",int(b+i)-(int)c ); b[i].~BBB(); } (配置newを使って配列確保し、個々に対してデストラクタを呼んでいる、つもりなのですが、これは正しいのでしょうか?) 4.そもそも、配置newでない、また再定義していないデフォルトのnewはアラインメント的に大丈夫なことが保証されてて、したがって通常はこんな面倒なことをしなくても問題ない、のでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.4

配列new ですが, ちょっと規格から引っ張ってみますと ・new T[5] は operator new[](sizeof(T)*5+x) を呼び出す ・new(2, f) T[5] は operator new[](sizeof(T)*5+y, 2, f) を呼び出す とあります. この x や y が #2 の最初で気にした値ですが, この値について規格では ・非負である (当然) ・(配列) new の呼び出しごとに違ってもよい としています. 従って, 処理系を決めない限りこの値を事前に決め打ちすることは不可能です (もっといえば, 処理系が決まっていたとしても「事前に」決まるとは限らない). そして, 配置new がどのように動作するかにもよりますが, 第1引数の値 (上の例だと sizeof(T)*5+y) が確保した大きさを超えている場合にはおかしなことになるかもしれません. でもって, 今の C++ では「ポインタと行って帰ってできる整数型」は定義されていません. ほとんどの場合 size_t でいいと思いますが, 規格上の保証はないような気がする. あ... じっと規格を見ていたら 1 がまずいことに気づいてしまった.... 今の例では BBB がデストラクタを持っているので POD ではなく, してがってそのオブジェクトをメンバに持つ alignmentof_<BBB>::S も POD ではありません. そして, offsetof は「POD な構造体または共用体にしか使えない」と (今の規格には) 書いてあるのでアウト.... ついでにいうと Align<T, B>::address も微妙に怪しい. 「アラインメントが 2のべき」じゃないシステム (そんなのあるのか?) に遭遇するとダメだ.

LongSecret
質問者

お礼

>でもって, 今の C++ では「ポインタと行って帰ってできる整数型」は定義されていません. ほとんどの場合 size_t でいいと思いますが, 規格上の保証はないような気がする. では 後でもしものことが(ないとは思いますが)あった場合に、 typedef size_t 新しい型名; というのを自分で定義しておけば、僅かな手間で直せるので、念には念のため、ならば、現実的にはこうしておくことにしましょう。 > 「アラインメントが 2のべき」じゃないシステム かつ、アラインメントに寛容ではない、ですね? しかし、ありえたとして現実的には現状から普及できるでしょうか… もしあった場合も考えるなら 単に式を変えるか コンパイル時に2のべきにならない場合はコンパイルエラーを出すようにテンプレートをもう少し改造しつつ、対象を限定する 等の方法で対処することにしましょう。 問題はその他の2点です。 boostにそっくり同じ目的のものがあることを知ったのですが boost::alignment_of<T>::value は、非POD(日本語版の規格書的に書くと非C互換型?)対応なのでしょうか? また、対応しているとすれば、どこら辺が決め手になっているのでしょうか?(#defineが多すぎて把握するが難しいです) >・new T[5] は operator new[](sizeof(T)*5+x) を呼び出す >・new(2, f) T[5] は operator new[](sizeof(T)*5+y, 2, f) を呼び出す 及び、規格では未定 というのは確認できましたが、それでは 配列newに関しては 配置newとして使わない、以外に どのように対処することができるでしょうか? あともうちょい別件ですが char c[sizeof(BBB)]; BBB* b=new(c) BBB; はまずいけど char* c=new char[sizeof(BBB)]; BBB* b=new(c) BBB; はOKと読めなくもない感じがするのですが 実際にこの解釈でいいのでしょうか?

LongSecret
質問者

補足

ちょっと意味が伝わりにくいかもしれないので --------- 配置newとして使わない、以外に どのように対処することができるでしょうか? ↓ 配置newとして使いたい場合は どのようにすれば対処することができるでしょうか? または、配置newとして使わない、以外には規格上完全な保証を得る方法はないのでしょうか?

その他の回答 (3)

  • BLK314
  • ベストアンサー率55% (84/152)
回答No.3

C/C++ではポインタのサイズについて定めていません。 64ビットを視野に入れると longとポインタの互換性があるとは言い切れません。 http://ja.wikipedia.org/wiki/64%E3%83%93%E3%83%83%E3%83%88 LLP64(Win64等)ではlongは32ビットに対し、ポインタは64ビットなので アドレスをlongに代入することは出来ません。 (UINTも同じ) 強いて言えば、size_tが64ビットです。(64ビットコンパイル時) 32ビットとの互換性を持たせたいときはsize_tを使います。 http://d.hatena.ne.jp/Schima/20091101/1257052506

LongSecret
質問者

お礼

>LLP64(Win64等)ではlongは32ビット 確かにそう書いてありますねw ありがとうございます♪ 現実解としてはsize_tあたりになるということですね?

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

あっと, そういえば ・配列を確保する場合には, 「オブジェクトに必要な分」より多いメモリが必要かもしれない (つまり A[5] を確保するためのメモリは 5*sizeof(A) より多いかもしれない) ・C++ には UINT なんて型は存在しないし, unsigned int のことだとしてもこれがポインタと互換であるとは限らない という問題があるんだった.

LongSecret
質問者

お礼

ありがとうございます。 前者については上記コードからSYSTEM_ALIGNを取り払うとして char c[sizeof(BBB)*10+alignof(BBB)-1]; で埋まらないかもしれない、オブジェクトの配列確保という意味での別件ということでしょうか?(sizeofはアラインメント済みの数値のはずなので単体確保10回ならおそらくこの式で問題ないと思うのですが…) それのサイズを知る方法はありますか? 後者については UINTはWinDef.h インクルードしてるという前提でしたw つまり確かにunsigned intのことですが、ポインタと必ず互換性があるのはlong型でしたでしょうか?

LongSecret
質問者

補足

(約2年後、この補足が最後に書かれました) まぁとりあえず、今現在まで困ったこともなかった(配置newを配列で使わなくても)ので この質問は解決として置きますね。 どうもありがとうございました♪

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

operator new が呼び出すメモリ割り当て関数については規格 (5.3.4 節のパラグラフ 10) に Because allocation functions are assumed to return pointers to storage that is appropriately aligned for objects of any type とあるので, デフォルトの operator new ではアラインメントについて考える必要はない (はず) です.

LongSecret
質問者

お礼

ありがとうございます♪ > デフォルトの operator new ではアラインメントについて考える必要はない (はず) です. 安心しました。 ちょうどTacosanさんにお聞きしたかったのですが、規格書を調べるときに効率的な方法ってありますか? 今のところ英語版の購入はしていないのですが、少なくとも日本語版のPDFをネット上で見たとき、検索機能があんまりうまくないというか、基本的な調べ方がわからないので、全部見てると時間がかかってしまいます。

LongSecret
質問者

補足

ん、まてよ char のアラインメントは1だから #define SYSTEM_ALIGN 8 なんてものを取っ払って template<class T, class U> struct A_l_i_{ enum { Small = alignof(T) <= alignof(U) }; }; 及び #define ALISS(T,U,P) (Aligne<T,A_l_i_<T,U>::Small>::address(P)) としておいて BBB* bb = new( ALISS(BBB,char,ccc) ) BBB[10]; とするか #define ALISS(T,U,P) (Aligne<T,A_l_i_<T,U>::Small>::address(P)) #define ALISS1(T,P) (Aligne<T,A_l_i_<T,char>::Small>::address(P)) としといて BBB* bb = new( ALISS1(BBB,ccc) ) BBB[10]; にしておかないとまずいですかね。