私はここ数年C言語をあまり使っていません。この質問今日、私はよく知らない C 構文に遭遇しました。
どうやらC99次の構文が有効です:
void foo(int n) {
int values[n]; //Declare a variable length array
}
これはかなり便利な機能のように思えます。これを C++ 標準に追加することについて議論されたことはありますか? また、もしあったとしたら、なぜ省略されたのですか?
考えられる理由としては、次のようなものが挙げられます。
- コンパイラベンダーが実装するのは困難
- 標準の他の部分と互換性がない
- 機能は他のC++構造でエミュレートできる
C++ 標準では、配列のサイズは定数式でなければならないと規定されています (8.3.4.1)。
はい、もちろん、おもちゃの例では を使用できることはわかっていますstd::vector<int> values(m);
が、これはスタックではなくヒープからメモリを割り当てます。そして、次のような多次元配列が必要な場合:
void foo(int x, int y, int z) {
int values[x][y][z]; // Declare a variable length array
}
このvector
バージョンはかなり不格好になります:
void foo(int x, int y, int z) {
vector< vector< vector<int> > > values( /* Really painful expression here. */);
}
スライス、行、列もメモリ全体に分散される可能性があります。
での議論を見ると、comp.std.c++
この問題は議論の両側に非常に有力な人物がいて、かなり物議を醸していることがわかります。 がstd::vector
常により良い解決策であるかどうかは、確かに明らかではありません。
ベストアンサー1
(背景: C および C++ コンパイラの実装経験があります。)
C99 の可変長配列は基本的に失敗でした。VLA をサポートするために、C99 は常識に対して次のような譲歩をする必要がありました。
sizeof x
は、もはや常にコンパイル時の定数ではありません。コンパイラは、sizeof
実行時に -式を評価するためのコードを生成する必要がある場合があります。2 次元 VLA (
int A[x][y]
) を許可するには、2D VLA をパラメータとして受け取る関数を宣言するための新しい構文が必要でしたvoid foo(int n, int A[][*])
。C++ の世界ではそれほど重要ではありませんが、C の対象ユーザーである組み込みシステム プログラマーにとっては非常に重要なことですが、VLA を宣言することは、スタックの任意の大きなチャンクをむさぼり食うことを意味します。これは、スタックオーバーフローとクラッシュを確実に引き起こします。( を宣言するときはいつでも
int A[n]
、2GB のスタックの余裕があることを暗黙的に主張しています。結局のところ、「n
ここでは が確実に 1000 未満である」ことがわかっている場合は、 を宣言するだけですint A[1000]
。 を 32 ビット整数に置き換えることn
は1000
、プログラムの動作がどうあるべきかをまったく知らないことを認めていることになります。)
さて、それでは C++ についてお話ししましょう。C++ では、C89 と同様に「型システム」と「値システム」が明確に区別されていますが、C ではそうではなかった方法で、実際にそれに依存し始めています。たとえば、次のようになります。
template<typename T> struct S { ... };
int A[n];
S<decltype(A)> s; // equivalently, S<int[n]> s;
がn
コンパイル時定数でなかったら(つまり、A
可変に変更可能な型だったとしたら)、 の型はいったい何になるのでしょうかS
?S
の型も実行時にのみ決定されるのでしょうか?
これはどうですか:
template<typename T> bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);
コンパイラは のインスタンス化のためのコードを生成する必要があります。そのコードはどのようになるでしょうか?コンパイル時にmyfunc
の型がわからない場合、そのコードを静的に生成するにはどうすればよいでしょうか?A1
さらに悪いことに、実行時に となりn1 != n2
、 となったらどうなるでしょうか!std::is_same<decltype(A1), decltype(A2)>()
? その場合、テンプレートの型推論が失敗するため、 の呼び出しはmyfunc
コンパイルすらされないはずです。実行時にその動作をエミュレートするにはどうすればよいでしょうか?
基本的に、C++ は、テンプレート コード生成、関数評価など、ますます多くの決定をコンパイル時に押し込む方向に進んでいます。一方、C99 は、従来のコンパイル時の決定 (例) を実行時に押し込むことに忙しくしていました。これを考慮すると、 C99 スタイルの VLA を C++ に統合しようとする努力を費やすことは本当に意味があるのでしょうか。constexpr
sizeof
他の回答者がすでに指摘しているように、C++ には、本当に「必要な RAM の量がわからない」というアイデアを伝えたい場合に使用できるヒープ割り当てメカニズム (std::unique_ptr<int[]> A = new int[n];
またはstd::vector<int> A(n);
明らかなメカニズム) が多数用意されています。また、C++ には、必要な RAM の量が RAM の容量よりも大きいという避けられない状況に対処するための気の利いた例外処理モデルが用意されています。しかし、この回答によって、C99 スタイルの VLA が C++ に適していない理由、さらには C99 にも適していない理由がよくわかると思います。;)
このトピックの詳細については、N3810「配列拡張の代替手段」、Bjarne Stroustrup の 2013 年 10 月の VLA に関する論文。Bjarne の視点は私のものとは大きく異なります。N3810 は、物事に適した C++ 風の構文を見つけることと、C++ での生の配列の使用を控えることに重点を置いていますが、私はメタプログラミングと型システムへの影響に重点を置いています。メタプログラミング/型システムへの影響が解決済み、解決可能、または単に興味がないと彼が考えているかどうかはわかりません。
同じ点を多く取り上げている良いブログ記事は「可変長配列の正当な使用」(クリス・ウェロンズ、2019年10月27日)。