[メモ]C言語構造体のポインターフィールドは、インラインで配列を渡して設定できない

# 注意：本記事のタイトル・内容は不正確です（2018/09/08 追記）
初出時の内容では構造体のポインターフィールドを「配列」で初期化していませんでした。（コメントありがとうございます）

ポインターを配列で初期化するためには、初出時のとおり配列を別に定義して参照するか、あるいはコメントでいただいた「複合リテラル」を使うか、いずれかの方法を取る必要があります[^__]。

[^__]: 文字列リテラルでポインターを初期化する方法もありますが、文字列リテラルが「配列」と同じかどうか、規格書で確認が取れていません。実装上、文字列リテラルが配列のように振る舞うことは確かだとおもいますが……。

```c
struct foo { char *p; };

/* method 1:  initialize by defined array. */
char hello[] = {'h', 'e', 'l', 'l', 'o',};
struct foo f1 = {hello};

/* method 2: initialize by compound literal. */
struct foo f2 = {(char[]){'h', 'e', 'l', 'l', 'o',}};
```

間違いの原因は「初期化リスト」を「配列」を混同したことです。これについて「[[メモ]C言語配列の本体](https://qiita.com/ryohji/items/1725c71b931f3de5b52c)」を書きました。本記事をお読みのみなさまの一助になれば幸いです。

以下は初出時のまま、変更を加えていません。読み進める場合は、配列と初期化リストの混同に注意してください。

----

# ポインターフィールドはインラインの配列で初期化できない
構造体に文字型ポインターを持たせたときに、これを文字列で初期化できる。

```C
struct foo {
  char *string;
};

void some_fn() {
  struct foo value = {"hello"};
  ...
```

この伝でフィールドを配列でも初期化できるか？　というと、不可。

```c
void another_fn() {
  struct foo value = {{'h', 'e', 'l', 'l', 'o', '\0'}};
  ...
```

コンパイラーに処理させると次のような警告が表示される。

```text
local_var.c:11:24: warning: incompatible integer to pointer conversion initializing 'char *' with an expression of type 'int' [-Wint-conversion]
  struct foo value = {{'h', 'e', 'l', 'l', 'o', '\0'}};
		       ^~~
local_var.c:11:29: warning: excess elements in scalar initializer
  struct foo value = {{'h', 'e', 'l', 'l', 'o', '\0'}};
                            ^~~
2 warnings generated.
```

コンパイラーのバージョンは次のとおり。

```text
Apple LLVM version 9.1.0 (clang-902.0.39.2)
Target: x86_64-apple-darwin17.7.0
```

## インライン配列でポインターフィールドを初期化できたとしたら？
配列ストレージの確保場所を考えると、これで初期化できたらまずそうだと想像できる。

`another_fn` で期待したように、その場で確保した配列のアドレスがポインターに暗黙で変換されるとする。そのようにして初期化した `value` を構造体のコピーで関数の外に戻すとする。

配列は `another_fn` のローカル変数として宣言されていたので、つまり、スタック上に確保されている。ここで `another_fn` の呼び出し元が別の関数を呼び出した状況を考えてみよう。

```c
struct foo another_fn() {
  struct foo value = {{'h', 'e', 'l', 'l', 'o', '\0'}};
  return value;
}

void print_foo(struct foo* foo) {
  int i;
  for (i = 0; i < strlen(foo->string); ++i) {
    printf("%c", foo->string[i]);
  }
}

int main() {
  struct foo value = another_fn();
  print_foo(&foo);
  ...
```

そもそも警告が出て期待したようにはコンパイルされないから想像してもらうしかないのだけれど、ローカル（スタック）に確保した配列を戻して、別の関数でスタックを利用したときに起きる事故は考えたくもない。

この場合は `print_foo` を呼び出した時点で、 `another_fn` が構築したスタック上の `struct foo value` は綺麗さっぱり破壊される。（`i = 0` によってゼロクリア、と言いたいところだけれど `print_foo` が引数をスタックにプッシュするので、そのアドレスで破壊される。どんな文字列長になってどれほどループが回るかは神のみぞ知る）

余談だけれど、次のように書けばコンパイルも通るし合法。暇があるようだったら、何が起きるか試してみるといい。

```c
struct foo another_fn() {
  char string[] = {'h', 'e', 'l', 'l', 'o', '\0'};
  struct foo value = {string};
  return value;
}
```

## 警告の解説

丁寧に警告を読むと、なにを間違っていてどう直せばいいのかヒントが得られる。

### incompatible integer to pointer conversion initializing 'char *' with an expression of type 'int'

以下 `struct foo` の定義に対して `value` の最初の `'h'` が整数値 `int` でありポインター `char *` と互換性がないことを警告されている。

```c
struct foo { char *string; };
struct foo value = {{'h', 'e', 'l', 'l', 'o', '\0'}};
```

コンパイルは通ってしまうので、 `'h'` の値が文字列を格納するアドレスだとみなされていることになる。

### excess elements in scalar initializer

`struct foo` はポインターをひとつしか持っていない。これに対して６つの要素を指定しているので初期化子の数が多すぎるよ！　という警告。

```c
struct foo { char *string; };
struct foo value = {{'h', 'e', 'l', 'l', 'o', '\0'}};
```

## 文字列のストレージ

以下が問題なく通る理由も見ておこう。

```c
struct foo { char *string; };
struct foo value = {"hello"};
```

これをアセンブリに変換してみる。

```sh
$ gcc -S -x c - <<EOF; cat ./-.s
> struct foo { char *string; };
> struct foo value = {"hello"};
> EOF
	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 13
	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"hello"

	.section	__DATA,__data
	.globl	_value                  ## @value
	.p2align	3
_value:
	.quad	L_.str


.subsections_via_symbols
```

はい。

コードセクション（text）に `"hello"` が確保されている。
構造体 `foo` は文字列ポインターと同じサイズでしかないためストリップされデータセクションにグローバルシンボル `_value` が確保される。そしてそのアドレスはコードセクションの `"hello"` に張られたラベル `L_.str` を指している。

C コード中の文字列リテラルは、初期化済み配列オブジェクトを指すシンタックスシュガーだとおもえばいい。