zero length array, flexible array

github.com

たまたま目に入ったこのコードを見ていて, zero length arrayって末尾のメンバ以外でも使えたんだって思って気になるところを調べてみた.

Flexible array

en.wikipedia.org

C99から入った機能. 配列の長さを宣言しないことで利用できる. sizeofはできない. 構造体などの末尾フィールドに置くことができる. 典型的な使い方は以下のような感じ.

struct Data {
   // some fields
   SomeData s[];
};

struct Data *d = malloc(sizeof(Data) + some_arbitrary_size);

ポインタを使ってしまうと構造体とこの末尾のデータがある場所は別の領域になってしまうわけだが, このようにすることで連続するメモリにすべて割り当てることができる.

構造体の末尾以外に置くことはできない

struct Data {
  int a[];
  int b;
};

このようなコードはエラーとなる

a.c:2:7: error: flexible array member not at end of struct
    2 |   int a[];
      |      

Zero length array

gcc.gnu.org

GCC拡張. C90時代から使えたもので, 宣言時に配列サイズを 0とする. Flexible arrayと違い構造体に何個でも宣言でき, GCCでは sizeofを行った場合 0が返るものと規定している.

struct Data {
    int a;
    unsigned char b[0];
    int c[0];
};

このように宣言した場合, bc は同じ先頭アドレスを持ち実質的に union のようになる.

#include <stdio.h>
#include <stdlib.h>

struct Data {
    int a;
    unsigned char b[0];
    int c[0];
};

int main() {
    struct Data *d = malloc(sizeof(int) * 2);
    printf("&d.a=%p\n", &d->a);
    printf("&d.b=%p\n", &d->b);
    printf("&d.c=%p\n", &d->c);

    d->b[0] = 0;
    d->c[0] = 0xdeadbeef;

    printf("d.b[0]=%x\n", d->b[0]);
    printf("d.c[0]=%x\n", d->c[0]);
    return 0;
}

結果は以下の通りで

&d.a=0x559613d242a0
&d.b=0x559613d242a4
&d.c=0x559613d242a4 // cは bと同じアドレス
d.b[0]=ef // cを書き換えると bも変わってしまう
d.c[0]=deadbeef

Flexible array, zero length arrayを使わない場合

今時そんな環境はないと思うが, C99以前かつ GCC拡張が使えない場合は長さ 1の char配列などを置いて代用していたように記憶している.

その他

github.com

上記の変更が含まれる PRにあった non temporalって何ぞやと思ってググると下記の解説があった.

lwn.net

Non-temporal in this context means the data will not be reused soon, so there is no reason to cache it.

とのこと. streamな dataで, 非常に計算速度が重要なものになるとそこまで意識する必要があるんですね. 計算する際キャッシュにうまく載せることは考えたことあったけど不要なものはそもそも載せないという発想がなかった.

いろいろ学びがあった.