compound-literal - コンパウンドリテラル

目的

C99の拡張機能(GCCでも独自に拡張している)である、コンパウンドリテラルについて知る。

結果

コンパウンドリテラルとは、無名変数を記述する方法で、C99での拡張仕様。
文法的にはキャストと中括弧での変数定義を組み合わせて記述する。
gccでは、グローバルスコープでのコンパウンドリテラルは.dataセクションに置かれる(.rodataに置かれることを期待する場合でも!)。

以下、詳細。

背景と動機

とあるソースで、以下のような記述があった。

sample.c
https://sssvn.jp/svn/spikelet/c/compound-literal/sample.c
#define NULL 0

struct tupple { int key; int val; };
const struct tupple list1[] = { { 1, 2 }, { NULL }, };
const struct tupple list2[] = { { 3, 4 }, { NULL }, };
const struct tupple list3[] = { { 5, 6 }, { NULL }, };

struct members {
  const char *name;
  const struct tupple **ptr;
};
const struct members member_list[] = {
  { "name-1", (const struct tupple *[]){ list1, list2, NULL } },
  { "name-2", (const struct tupple *[]){ list2, list3, NULL } },
};

最後の3行、member_list[]の定義の中に、見慣れない記述があった。
本当の型はポインタなのに、構造体の配列にキャストしている?
メモリイメージ的にはそれまずくないか?(ポインタ一個分の領域に配列を置いて、オーバーランしてしまっていないか?)

それはコンパウンドリテラル

この記述は、compound literal という、C99の仕様に沿った正しい記述だそうだ(参考→プログラミング言語 C の新機能 Part XXVIII: コンパウンドリテラル (Compound Literal) - seclan のほえほえルーム)。
キャストと構造体定義の文法を組み合わせて、無名の変数(構造体や配列)をその場で定義する仕組み。

GCC拡張としても説明されている(参考→Using the GNU Compiler Collection (GCC)
GCC拡張では、static 変数でもできるらしい(用例はあまり意味があるように思えないが)。

どうコンパイルされるのか

i386でのアセンブル結果(抜粋)

gcc -S -o sample.s sample.c の結果:

.globl member_list
	.align 4
	.type	member_list, @object
	.size	member_list, 16
member_list:
	.long	.LC0
	.long	__compound_literal.0
	.long	.LC1
	.long	__compound_literal.1
	.data
	.align 4
	.type	__compound_literal.0, @object
	.size	__compound_literal.0, 12
__compound_literal.0:
	.long	list1
	.long	list2
	.long	0
	.align 4
	.type	__compound_literal.1, @object
	.size	__compound_literal.1, 12
__compound_literal.1:
	.long	list2
	.long	list3
	.long	0
シンボル配置

nm sample.oの結果:

0000000c d __compound_literal.0
00000000 d __compound_literal.1
00000030 R list1
00000020 R list2
00000010 R list3
00000000 R member_list
状況の整理

__compound_literal.[01] というシンボルがあたらしく作られ、member_list からはそのシンボルを参照している。
たしかに、この記述ならバッファのオーバーランも起きない。

ただし、コンパウンドリテラルで記述した変数は、なぜか data セクションに置かれている(gcc-2.95からgcc-4.2.3まで同じ結果)。
それを格納した member_list などは read only data セクションに置かれているにもかかわらず。
なぜだろう?

なお、グローバル変数でないコンパウンドリテラルは、シンボルは作られず、コード中に埋め込まれるようだ(参考→フリーソフトウェア徹底活用講座(9))。
※あえて配列を作るほどでもないような場合も、シンボルは作られない様子

gccコンパウンドリテラルをどう処理するか

コンパウンドリテラルは、gcc 中では以下の箇所で処理される。
yaccベースの文法の方が追いやすいので、gcc-3.4.6 を参照した)

gcc/c-parse.y

primary ターゲットのルール中で登場。

primary:
...
	| '(' typename ')' '{'
		{ start_init (NULL_TREE, NULL, 0);
		  $2 = groktypename ($2);
		  really_start_incremental_init ($2); }
	  initlist_maybe_comma '}'  %prec UNARY
		{ tree constructor = pop_init_level (0);
		  tree type = $2;
		  finish_init ();

		  if (pedantic && ! flag_isoc99)
		    pedwarn ("ISO C90 forbids compound literals");
		  $$ = build_compound_literal (type, constructor);
		}

キャスト付きの複文が来たら、build_compound_literal() を呼び出す。

gcc/c-decl.c

グローバル変数のスコープで使用されている場合は、"__compound_literal.XX"(XXはオブジェクトファイル内の通し番号)というシンボルを作成する。

tree
build_compound_literal (tree type, tree init)
{
...
  TREE_STATIC (decl) = (current_scope == global_scope);
...
  TREE_READONLY (decl) = TREE_READONLY (type);
...
  if (TREE_STATIC (decl))
    {
      /* This decl needs a name for the assembler output.  We also need
	 a unique suffix to be added to the name.  */
      char *name;

      ASM_FORMAT_PRIVATE_NAME (name, "__compound_literal",
			       compound_literal_number);
      compound_literal_number++;
      DECL_NAME (decl) = get_identifier (name);
      DECL_DEFER_OUTPUT (decl) = 1;
      DECL_COMDAT (decl) = 1;
      DECL_ARTIFICIAL (decl) = 1;
      pushdecl (decl);
      rest_of_decl_compilation (decl, NULL, 1, 0);
    }

  return complit;
}
ソース解析

sample.c の例では __compound_literal.0 は .rodata に置いて欲しいのだが、どうやら TREE_READONLY(type) が 0 なため、TREE_READONLY(decl) も 0 となり、.data に置かれてしまうようだ。

試しに、この箇所を以下のように変更してgccコンパイルすると、

-  TREE_READONLY (decl) = TREE_READONLY (type);
+  TREE_READONLY (decl) = 1;

このgccコンパイルした sample.c は __compound_literal.X が .rodata セクションに置かれる。

nm sample.o
00000050 r __compound_literal.0
0000005c r __compound_literal.1
00000000 R list1
00000010 R list2
00000020 R list3
00000040 R member_list

おそらく、TREE_READONLY(type) の箇所をもう少し別の記述にすれば、定義箇所のconstを判別して READONLY か否かを判別できるようになるのだろうが、この辺で時間切れ。

ちなみに、配列ではなくポインタにしてみたら?

sample.cでの member_list の定義を、ポインタの配列ではなく、ポインタのポインタにするとどうなるか?

Cソース
sample2.c
https://sssvn.jp/svn/spikelet/c/compound-literal/sample2.c
#define NULL 0

struct tupple { int key; int val; };
const struct tupple list1[] = { { 1, 2 }, { NULL }, };
const struct tupple list2[] = { { 3, 4 }, { NULL }, };
const struct tupple list3[] = { { 5, 6 }, { NULL }, };

struct members {
  const char *name;
  const struct tupple **ptr;
};
const struct members member_list[] = {
  { "name-1", (const struct tupple **){ list1, list2, NULL } },
  { "name-2", (const struct tupple **){ list2, list3, NULL } },
};
コンパイルワーニング
gcc -S -o sample2.s sample2.c
sample2.c:13: warning: initialization from incompatible pointer type
sample2.c:13: warning: excess elements in scalar initializer
sample2.c:13: warning: (near initialization for `(anonymous)')
sample2.c:13: warning: excess elements in scalar initializer
sample2.c:13: warning: (near initialization for `(anonymous)')
....
アセンブル結果(抜粋)
member_list:
        .long   .LC0
        .long   list1
        .long   .LC1
        .long   list2

この場合は、配列の先頭の要素しか member_list に格納されていない。
バッファオーバーランが起きないだけましだが、要素が足りないのに気付かずに悩みそうだ。