構造体のサイズを調べるマクロ
最近、C・C++に触れる機会が多くなってまいりました。
触っているプログラムはTCPでデータを送受信しているのですが、そのおかげ(?)でエンディアンやらパディングという(訳の分からない)言葉を知るようになりました
構造体のメンバーの型、宣言する順番、配列サイズなどによって構造体のメモリ配置の中に使えない空き領域のことをパディングというらしいです。
そのため、構造体はsizeofで得られるサイズと、メンバーの型のサイズの合計値が必ずしも一致しません
なぜパディングができるのか・・・というと完全にハードウェアのお話
私は以下のサイトを参考にしました
データ型のアラインメントとは何か,なぜ必要なのか?
Cプログラミング診断室/管理は複雑に/パイト・オフセット
普通なプログラムを書くなら、パディングなんて気にしなくても問題無いと思いますが、「そんなことが起きている」って知ってしまうと、ついついこだわってしまいますねー
実際どんなことが起きているのか調べるために、構造体のメモリ上の配置を調べるようなプログラム(というよりマクロ)を作ってみました
構造体のメモリ上の配置を調べるマクロさんを以下のように定義しました
#if ! defined( StructInfoFunction_H_ ) #define StructInfoFunction_H_ #include <stdio.h> #include <stdlib.h> // 構造体先頭のアドレスから、メンバーが離れている距離を取得(Byte-Offset取得) #define GetByteOffset( structType, member ) \ (unsigned int)&( (((structType*)NULL)->member) ) // 構造体のメンバーのサイズを取得 #define GetSizeOfStructMember( structType, member ) \ sizeof( (((structType*)NULL)->member) ) // 構造体メンバーの使用領域を出力 #define StdOutAddressAreaOfStructMember( structType, member ) \ { \ printf( "\t"#structType" のメンバー %+2s の使用領域:%2d 〜 %2d\n", \ #member, \ GetByteOffset( structType, member ), \ /* 以下3行は、後ろの使用領域の計算 見やすいように無駄改行 */ \ GetByteOffset( structType, member ) \ + GetSizeOfStructMember( structType, member ) \ - 1 ); \ } \ // 構造体の全体のサイズを出力 #define StdOutStrctSize( structType ) \ { \ printf( "\t"#structType" の全体のサイズは %d [Byte]\n", \ sizeof ( structType ) ); \ } \ #endif /* end StructInfoFunction_H_ */
あれこれ調べられる構造体のみなさま
#if ! defined( StructDeclaration_H_ ) #define StructDeclaration_H_ // 宣言 int→int→doubleの順 typedef struct { int i1; int i2; double d; } TestIID; // 宣言 int→double→intの順 typedef struct { int i1; double d; int i2; } TestIDI; // char型の配列のみ typedef struct { char a[ 17 ]; char b[ 24 ]; char c[ 42 ]; } OnlyChar; // 適当に混ぜた typedef struct { double d; char a[ 17 ]; int i; char b[ 41 ]; } Mix; #endif /* end StructDeclaration_H_ */
マクロを使って実際に調べてみる処理
#include "StructDeclaration.h" #include "StructInfoFunction.h" int main( void ) { printf( "構造体 TestIID の調査\n" ); StdOutAddressAreaOfStructMember( TestIID, i1 ); StdOutAddressAreaOfStructMember( TestIID, i2 ); StdOutAddressAreaOfStructMember( TestIID, d ); StdOutStrctSize( TestIID ); printf( "\n" ); printf( "構造体 TestIDI の調査\n" ); StdOutAddressAreaOfStructMember( TestIDI, i1 ); StdOutAddressAreaOfStructMember( TestIDI, d ); StdOutAddressAreaOfStructMember( TestIDI, i2 ); StdOutStrctSize( TestIDI ); printf( "\n" ); printf( "構造体 OnlyChar の調査\n" ); StdOutAddressAreaOfStructMember( OnlyChar, a ); StdOutAddressAreaOfStructMember( OnlyChar, b ); StdOutAddressAreaOfStructMember( OnlyChar, c ); StdOutStrctSize( OnlyChar ); printf( "\n" ); printf( "構造体 Mix の調査\n" ); StdOutAddressAreaOfStructMember( Mix, d ); StdOutAddressAreaOfStructMember( Mix, a ); StdOutAddressAreaOfStructMember( Mix, i ); StdOutAddressAreaOfStructMember( Mix, b ); StdOutStrctSize( Mix ); printf( "\n" ); system( "Pause" ); return 0; }
実行結果
構造体 TestIID の調査 TestIID のメンバー i1 の使用領域: 0 〜 3 TestIID のメンバー i2 の使用領域: 4 〜 7 TestIID のメンバー d の使用領域: 8 〜 15 TestIID の全体のサイズは 16 [Byte] 構造体 TestIDI の調査 TestIDI のメンバー i1 の使用領域: 0 〜 3 TestIDI のメンバー d の使用領域: 8 〜 15 TestIDI のメンバー i2 の使用領域:16 〜 19 TestIDI の全体のサイズは 24 [Byte] 構造体 OnlyChar の調査 OnlyChar のメンバー a の使用領域: 0 〜 16 OnlyChar のメンバー b の使用領域:17 〜 40 OnlyChar のメンバー c の使用領域:41 〜 82 OnlyChar の全体のサイズは 83 [Byte] 構造体 Mix の調査 Mix のメンバー d の使用領域: 0 〜 7 Mix のメンバー a の使用領域: 8 〜 24 Mix のメンバー i の使用領域:28 〜 31 Mix のメンバー b の使用領域:32 〜 72 Mix の全体のサイズは 80 [Byte] 続行するには何かキーを押してください . . .
TestIIDとTestIDIはメンバーの宣言場所が違うだけですが、合計サイズが異なって出力されていますね
ハードウェアに関する知識も必要だなぁって改めて感じました まる