409

ここ数年、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::vectoraが常により良い解決策であることは確かに明らかではありません。

4

13 に答える 13

304

(背景:CおよびC ++コンパイラの実装経験があります。)

C99の可変長配列は、基本的には失敗でした。VLAをサポートするために、C99は常識に基づいて次の譲歩をしなければなりませんでした。

  • sizeof x常にコンパイル時定数ではなくなりました。コンパイラはsizeof、実行時に式を評価するためのコードを生成する必要がある場合があります。

  • 2次元VLA(int A[x][y])を許可するには、2DVLAをパラメーターとして受け取る関数を宣言するための新しい構文が必要でした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は従来のコンパイル時の決定(例)をランタイムconstexprにプッシュするのに忙しかった。これを念頭に置いて、 C99スタイルのVLAをC ++に統合しようと努力することは本当に意味がありますか?sizeof

他のすべての回答者がすでに指摘しているように、C ++は、「必要なRAMの量がわからない」という考えを本当に伝えたい場合に、多くのヒープ割り当てメカニズム(std::unique_ptr<int[]> A = new int[n];または明白なメカニズム)を提供します。std::vector<int> A(n);また、C ++は、必要なRAMの量がRAMの量よりも多いという避けられない状況に対処するための優れた例外処理モデルを提供します。しかし、うまくいけば、この回答が、C99スタイルのVLAがC ++に適していない理由、そして実際にはC99に適していない理由についての良いアイデアを提供します。;)


このトピックの詳細については、VLAに関するBjarneStroustrupの2013年10月の論文であるN3810「AlternativesforArrayExtensions」を参照してください。BjarneのPOVは私のものとは大きく異なります。N3810は、物事に適したC ++風の構文を見つけることに重点を置き、C ++でraw配列を使用しないようにすることに重点を置いていますが、メタプログラミングと型システムへの影響に重点を置いています。彼がメタプログラミング/型システムの影響を解決した、解決可能、または単に面白くないと考えているかどうかはわかりません。


これらの同じ点の多くに当てはまる良いブログ投稿は、「可変長配列の合法的な使用」(Chris Wellons、2019-10-27)です。

于 2014-02-03T03:01:16.997 に答える
239

最近、usenetで開始されたこれについての議論がありました:なぜC++0xにVLAがないのか

私は、スタック上に潜在的に大きなアレイを作成しなければならないことに同意しているように見える人々に同意します。これは通常、使用可能なスペースがほとんどないため、良くありません。引数は、事前にサイズがわかっている場合は、静的配列を使用できるということです。また、事前にサイズがわからない場合は、安全でないコードを記述します。

C99 VLAは、スペースを無駄にしたり、未使用の要素のコンストラクターを呼び出したりせずに小さな配列を作成できるという小さな利点を提供できますが、型システムにかなり大きな変更をもたらします(ランタイム値に応じて型を指定できる必要があります-これnew演算子の型指定子を除いて、現在のC ++にはまだ存在していませんが、ランタイムネスがnew演算子のスコープから逃れることがないように、特別に扱われます)。

を使用することはできますstd::vectorが、動的メモリを使用するため、まったく同じではありません。また、独自のスタックアロケータを使用することは簡単ではありません(配置も問題になります)。また、VLAは固定サイズであるのに対し、ベクトルはサイズ変更可能なコンテナーであるため、同じ問題は解決されません。C ++ Dynamic Arrayの提案は、言語ベースのVLAの代わりに、ライブラリベースのソリューションを導入することを目的としています。ただし、私が知る限り、C++0xの一部にはなりません。

于 2009-12-11T10:28:54.903 に答える
30

必要に応じて、いつでもalloca()を使用して、実行時にスタックにメモリを割り当てることができます。

void foo (int n)
{
    int *values = (int *)alloca(sizeof(int) * n);
}

スタックに割り当てられるということは、スタックがほどけるときに自動的に解放されることを意味します。

クイックノート:alloca(3)のMac OS Xのマニュアルページに記載されているように、「alloca()関数はマシンとコンパイラに依存します。その使用は推奨されていません。」ちょうどあなたが知っているので。

于 2009-12-11T10:31:23.570 に答える
20

私自身の仕事では、可変長の自動配列やalloca()のようなものが必要になるたびに、メモリがCPUスタックに物理的に配置されていることを気にせず、一般的なヒープへの低速トリップを発生させなかったスタックアロケータ。したがって、可変サイズのバッファをプッシュ/ポップできるメモリを所有するスレッドごとのオブジェクトがあります。一部のプラットフォームでは、これをmmu経由で拡張できます。他のプラットフォームには固定サイズがあります(通常、mmuがないため、固定サイズのCPUスタックも伴います)。私が使用しているプラ​​ットフォームの1つ(ハンドヘルドゲームコンソール)は、メモリが少なく高速なメモリにあるため、とにかく貴重なCPUスタックがほとんどありません。

可変サイズのバッファをCPUスタックにプッシュする必要がないと言っているのではありません。正直なところ、これが標準ではないことに気付いたとき、私は驚きました。コンセプトが言語に十分に適合しているように見えるからです。しかし、私にとっては、「可変サイズ」と「CPUスタックに物理的に配置する必要がある」という要件が一緒になったことはありません。スピードが重要だったので、自分で「データバッファ用の並列スタック」を作りました。

于 2013-03-21T17:05:05.303 に答える
15

ヒープメモリの割り当ては、実行される操作に比べて非常にコストがかかる場合があります。例は行列数学です。小さい行列を使用して5〜10個の要素を処理し、多くの演算を実行する場合、mallocのオーバーヘッドは非常に重要になります。同時に、サイズをコンパイル時定数にすることは、非常に無駄で柔軟性がないように見えます。

C ++自体は非常に安全ではないため、「安全でない機能を追加しないようにする」という主張はあまり強くないと思います。一方、C ++は間違いなく最もランタイム効率の高いプログラミング言語機能であり、常に便利です。パフォーマンスが重要なプログラムを作成する人は、大部分がC ++を使用し、可能な限り多くのパフォーマンスを必要とします。ヒープからスタックにものを移動することは、そのような可能性の1つです。ヒープブロックの数を減らすことも別の方法です。オブジェクトメンバーとしてVLAを許可することは、これを実現する1つの方法です。私はそのような提案に取り組んでいます。確かに、実装は少し複雑ですが、かなり実行可能のようです。

于 2011-01-22T19:33:36.713 に答える
13

それはC++14で利用可能になるようです:

https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimension_arrays

更新:C++14にはなりませんでした。

于 2013-08-13T10:40:51.410 に答える
7

これはC++/ 1xに含まれると考えられていましたが、削除されました(これは、前に言ったことに対する修正です)。

std::vectorすでにこの役割を果たさなければならないので、とにかくC++ではあまり役に立ちません。

于 2009-12-11T10:26:33.647 に答える
2

これにはstd::vectorを使用します。例えば:

std::vector<int> values;
values.resize(n);

メモリはヒープに割り当てられますが、これにはわずかなパフォーマンス上の欠点しかありません。さらに、サイズがかなり制限されているため、スタックに大きなデータブロックを割り当てないことをお勧めします。

于 2009-12-11T10:22:57.803 に答える
2

このような配列はC99の一部ですが、標準のC++の一部ではありません。他の人が言っているように、ベクトルは常にはるかに優れたソリューションです。これが、可変サイズの配列がC ++標準(または提案されているC ++ 0x標準)にない理由です。

ところで、C ++標準が「なぜ」あるのかという質問については、モデレートされたUsenetニュースグループcomp.std.c++がアクセス先です。

于 2009-12-11T10:25:33.430 に答える
1

C99はVLAを許可します。また、VLAの宣言方法にいくつかの制限があります。詳細については、規格の6.7.5.2を参照してください。C++はVLAを許可しません。しかし、g++はそれを許可します。

于 2012-07-31T05:56:39.070 に答える
0

VLAは、VariablyModifiedタイプのより大きなファミリの一部です。このタイプのファミリーは、ランタイムコンポーネントを持っているため、非常に特別です。

コード:

int A[n];

コンパイラーは次のように認識します。

typedef int T[n];
T A;

配列の実行時サイズは変数ではなく、変数Aにバインドされていることに注意してください。

このタイプの新しい変数を作成することを妨げるものは何もありません。

T B,C,D;

またはポインタまたは配列

T *p, Z[10];

さらに、ポインターを使用すると、動的ストレージを使用してVLAを作成できます。

T *p = malloc(sizeof(T));
...
free(p);

VLAはスタックにのみ割り当てることができるという一般的な神話を払拭するもの。

質問に戻ります。

このランタイムコンポーネントは、C++タイピングシステムのベースの1つである型推論ではうまく機能しません。テンプレート、控除、およびオーバーロードを使用することはできません。

C ++タイピングシステムは静的であり、すべてのタイプはコンパイル中に完全に定義または推定される必要があります。VMタイプは、プログラムの実行中にのみ完了します。すでに地獄のように複雑なC++にVMタイプを導入するという追加の複雑さは、単に不当であると見なされていました。主な理由は、それらの主な実用的なアプリケーションがint A[n];、の形式の代替を持つ自動VLA()であるためですstd::vector

VMタイプは、多次元配列を処理するプログラムに非常に洗練された効率的なソリューションを提供するため、少し悲しいです。

Cでは、次のように書くことができます。

void foo(int n, int A[n][n][n]) {
  for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
      for (int k = 0; k < n; ++k)
        A[i][j][k] = i * j * k;
}

...

int A[5][5][5], B[10][10][10];
foo(5, A);
foo(10, B);

次に、C++で効率的でエレガントなソリューションを提供してみてください。

于 2021-09-16T22:12:48.117 に答える
-3

コンパイル時の値がわかっている場合は、次の操作を実行できます。

template <int X>
void foo(void)
{
   int values[X];

}

編集:アロケータはテンプレートパラメータであるため、スタックアロケータ(alloca)を使用するベクトルを作成できます。

于 2009-12-11T10:26:49.663 に答える
-8

私は実際に私のために働いた解決策を持っています。何度も実行する必要のあるルーチンの断片化のため、メモリを割り当てたくありませんでした。答えは非常に危険なので、自己責任で使用してください。ただし、アセンブリを利用してスタックのスペースを予約します。以下の私の例では、文字配列を使用しています(明らかに、他のサイズの変数はより多くのメモリを必要とします)。

void varTest(int iSz)
{
    char *varArray;
    __asm {
        sub esp, iSz       // Create space on the stack for the variable array here
        mov varArray, esp  // save the end of it to our pointer
    }

    // Use the array called varArray here...  

    __asm {
        add esp, iSz       // Variable array is no longer accessible after this point
    } 
}

ここでの危険はたくさんありますが、いくつか説明します。1.変数のサイズを途中で変更すると、スタックの位置が失われます。2.配列の境界を超えると、他の変数と可能なコードが破壊されます。3.これは64ビットでは機能しません。ビルド...そのアセンブリには別のアセンブリが必要です(ただし、マクロでその問題を解決できる場合があります)。4.コンパイラ固有(コンパイラ間を移動する際に問題が発生する可能性があります)。試したことがないのでよくわかりません。

于 2014-01-15T08:40:03.757 に答える