C言語のフレキシブル配列メンバ(flexible array member)、通称struct hack

PHPの内部実装がC言語なのは周知の事実かと思いますが、最近仕事でPHP7の内部実装をみる機会がありました。
そこで学んだC言語のflexible array member、通称struct hackというテクニックがなかなか面白かったので、忘れないようメモです。

ちなみに、このサイトの説明がわかりやすいです。
https://www44.atwiki.jp/bokuyo/pages/118.html

struct hack

struct hackはどんなテクニックかというと、こんな風に構造体の最後を要素数を定義しない配列にしてもコンパイル通りますよ!っていうテクニックです。
別に構造体の中なら最後じゃなくてもコンパイルは通ると思うんですが、たぶん最後以外はうまい使い方はないです。

struct Struct{
  char hoge;  
  char fuga[];
};

ちなみに要素数を指定しない配列は構造体内でしか宣言できなくて、それ以外の場所で使おうとするとコンパイルエラーになります。

#include <iostream>

int main(){   
  char piyo[];
  return 0;   
}
% g++ structHack.cc             
structHack.cc: In function ‘int main()’:
structHack.cc:10:13: error: storage size of ‘piyo’ isn’t known
   char piyo[];
             ^

ちなみにサイズは0バイト、つまり要素数0の配列と認識されるようです。

#include <iostream>                         
                                            
struct Struct{                              
  char hoge;                                
  char fuga[];                              
};                                          
                                            
int main(){                                 
  std::cout << sizeof(Struct) << std::endl; // 1と出力
  return 0;                                 
}

struct hackの使いどころ

で、コンパイル通るのわかったけど、こんなん何に使うのよ?っていう話なんですが、こんな感じで使います。

#include <iostream>                                                  
#include <stdlib.h>                                                  
                                                                     
struct Struct{                                                       
  char hoge;                                                         
  char fuga[];                                                       
};                                                                   
                                                                     
int main(){                                                          
  Struct* piyo = (Struct*)malloc(sizeof(Struct) + sizeof(char)*10); // 無理やりメモリ領域を確保
  piyo->fuga[8] = 100; // 要素数を定義してないのに安全にアクセスできる!!
  
  free(piyo);                                                                   
  return 0;                                                          
}

この時のメモリの領域はこんな感じに確保されるので、メモリを解放するまで安全にアクセスできます。
しかも、malloc時にはじめてfugaの要素数を決められるので、構造体毎に異なる要素数を指定できます。

余談:PHP7でのstruct hackの使われ方

PHP7ではstruct hackはこんな感じで使われています。
properties_table[]ではなくproperties_table[1]と書いてるのは、おそらくC99より前のバージョンのC言語ではサイズ0の配列の宣言ができなかったらしいので、その辺の名残りでしょう。

struct _zend_object {
	zend_refcounted_h gc;
	uint32_t          handle; // TODO: may be removed ???
	zend_class_entry *ce;
	const zend_object_handlers *handlers;
	HashTable        *properties;
	zval              properties_table[1]; // <- struct hack!!
};

さらに余談ですが、PHP5とPHP7ではこのzend_objectの構造が変わっているため、PHP7ではzend_objectを使って構造体を定義するときは、こんな風にzend_objectを最後に持ってくるようにする必要があります。

struct Struct2 {
   void  *hogehoge;
   zend_object std; // 絶対に最後じゃなきゃいけない。PHP5では最初に書くのが一般的だった(らしい)
};

参考
https://qiita.com/hnw/items/d615e0d4122f247d4c75
https://www.buildinsider.net/language/clang/01

コメント