[]の間に指定されるstatic

In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.

C99 でも 6.7.5.2 の 1 にかかれているが完全に同一の文となっている。

  • 任意の型修飾子とキーワード static に加えて、式または *[] の中に含める(MAY)。
  • もし[]が式(配列の長さを指定)を含むのならば、その式は整数(integer)型を持つ(SHALL)。
  • もしその式が定数ならば、その式は0より大きな値を持つ(SHALL)。
  • 要素の型は不完全型または関数型であってはならない(SHALL NOT)。(訳注:要素の型は関数ポインタは可)
  • 任意の型修飾子とキーワード static は、配列型の関数パラメータ宣言の中のみに記述され、 最も外側のarray type derivationのみに記述される(SHALL)。

ここで関数引数宣言の配列に限ってのみ[]の中に修飾子を書いて良いと書かれていて、 たとえば restrict キーワードがこれに該当する。 ここにて型修飾子とは別枠で static を書けると言っている。 詳細は関数の方に書いていて、下記のとおりである:

A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

こちらも C99 でも 6.7.5.3 の 7 にかかれているが完全に同一の文となっている。

  • typeの配列としてパラメータ宣言は、typeへのqualified pointerに調整される(SHALL)。
  • ここで型修飾子は(もしあれば) array type derivation の[]の間に指定される。
  • もしキーワード static が array type derivation の[]の間に指定されるならば、 それぞれの関数呼び出しに対して、 対応する実際の引数の値は配列の最初の要素についてサイズ指定の数以上参照できなければならない(SHALL)。

要するに下記のとおりということだ:

void f(int a[static 3]);
void g(void) {
      int x2[2] = {0};
      int x3[3] = {0};
      int x4[4] = {0};
      
      f(x4); // OK
      f(x3); // OK
      f(x2); // NG
}

課題コード EXAMPLE 4

課題コードとして、規格の下記コードスニペットで考える (gccでコンパイルするときは--std=c11指定すること)。 まずは static キーワードのことはなしで考察する。

EXAMPLE 4 The following prototype has a variably modified parameter.

void addscalar(int n, int m,
      double a[n][n*m+300], double x);
int main()
{
      double b[4][308];
      addscalar(4, 2, b, 2.17);
      return 0;
}
void addscalar(int n, int m,
      double a[n][n*m+300], double x)
{
      for (int i = 0; i < n; i++)
            for (int j = 0, k = n*m+300; j < k; j++)
                  // a is a pointer to a VLA with n*m+300 elements
                  a[i][j] += x;
}

ところで、このすぐ下にこんな例がある:

EXAMPLE 5 The following are all compatible function prototype declarators.

double maximum(int n, int m, double a[n][m]);
double maximum(int n, int m, double a[*][*]);
double maximum(int n, int m, double a[ ][*]);
double maximum(int n, int m, double a[ ][m]);

as are:

void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);
void f(double a[restrict static 3][5]);

(Note that the last declaration also specifies that the argument corresponding to a in any call to f must be a non-null pointer to the first of at least three arrays of 5 doubles, which the others do not.)

よって、addscalar のプロトタイプ宣言は下記のようにも変更可能。

void addscalar(int n, int m,
      double a[][*], double x);

ところで下記のようにすると、a は不完全型ということでコンパイルが通らない。

void addscalar(int n, int m,
      double a[][], double x); // compile error!

少なくともふたつ目の[]の間には数字を入れなくてはいけない。

単純に定数値を入れるとすると例えば下記のような感じになると思われる。 (C言語規格解釈のためのコードであって使い物にならないコードスニペットになってしまっているが。)

void addscalar(int n, int m,
      double a[][308], double x);
// (snip)
void addscalar(int n, int m,
      double a[][308], double x)
{
      // assert(n*m+300 == 308);
      for (int i = 0; i < n; i++)
            for (int j = 0, k = 308; j < k; j++)
                  a[i][j] += x;
}

ここで308をn*m+300に可変長配列化したものがExample 4のコードということみたいだ。

そもそもひとつ目は単なるqualified pointerに過ぎないのだから

void addscalar(int n, int m,
      double (* const a)[n*m+300], double x);
// (snip)
void addscalar(int n, int m,
      double (* const a)[n*m+300], double x)
{
      for (int i = 0; i < n; i++)
            for (int j = 0, k = n*m+300; j < k; j++)
                  // a is a pointer to a VLA with n*m+300 elements
                  // i.e. *(a + i) is a VLA with n*m+300 elements
                  (* (a + i))[j] += x;
}

と書けば、”a is a pointer to a VLA …” であって “a is a VLA …” ではないことが明瞭にわかる。

EXAMPLE 4 の []の間にstatic

まず、本節のタイトル通りのことを何も考えずにやると

void addscalar(int n, int m,
      double a[static n][static n*m+300], double x); // compile error!

というコードになるが、コンパイルエラーになる。 前節で書いたとおり n*m+300 は(可変長)配列長そのものだからである。

多次元配列の一番左の[]以外は(可変長)配列の長さを指定しなければならないので、 規格としては最初に挙げたもののonly in the outermost array type derivation.に あるように、[]の間に static を入れるとしても一番左の[]だけである。

void addscalar(int n, int m,
      double a[static n][n*m+300], double x);

さて、一番左の[]における static のない配列長の指定は、 特に意味のないものとして知られている。 例えば、下記コード中の sizeof() はすべて同じ値を返す。

void addscalar(int n, int m,
      double a[n][n*m+300], double x)
{
      sizeof(a);
      sizeof(double (*)[n*m+300]);
      sizeof(void*);
}

ここで例え a[static n][10] としても配列長がn以上としか意味しない。 よって、下記コード中の sizeof() はやはりすべて同じ値を返す。

void addscalar(int n, int m,
      double a[static n][n*m+300], double x)
{
      sizeof(a);
      sizeof(double (*)[n*m+300]);
      sizeof(void*);
}

であるならば [static n] が意味がありそうなのは、 呼び出し時の範囲確認ぐらいである。

試しに下記のようなコードにしてみた。

void addscalar(int n, int m,
      double a[static n][n*m+300], double x);
int main()
{
      double b[2][308];
      addscalar(4, 2, b, 2.17); // NG
      return 0;
}

このとき、明らかに addscalar() の呼び出し引数の配列サイズはオーバーしているので、 なんとかしてほしいものであったが、LLVM Clang も GCC も何も警告はなかった。

もうしばらく[static n]は何も意味のないので使わない時期が続きそうだ。

参考文献