Visual StudioのC2131原因と対処法:C++で可変長2次元配列(VLA)が作れない時はstd::vectorで解決

C++で行数R・列数Cを実行時に入力し、int a[R][C];のように2次元配列を作ろうとすると、Visual Studio(MSVC)ではC2131: expression did not evaluate to a constantで止まることがあります。原因は「可変長配列(VLA)」とC++標準の仕様差です。この記事では、MSVCでも確実に動く実装へ置き換える方法を、用途別にわかりやすく整理します。

目次

Visual StudioでC2131が出る理由:配列サイズは「コンパイル時定数」が必要

まず、次のようなコードを想定します。

#include <iostream>

int main() {
    int R, C;
    std::cin >> R >> C;

    int a[R][C]; // ← Visual Studio(MSVC)だとC2131になりがち

    for (int i = 0; i < R; ++i)
        for (int j = 0; j < C; ++j)
            std::cin >> a[i][j];

    for (int i = 0; i < R; ++i) {
        for (int j = 0; j < C; ++j) std::cout << "\t" << a[i][j];
        std::cout << "\n";
    }
}

MSVCのエラーC2131は、ざっくり言うと「ここはコンパイル時に確定する定数式じゃないとダメなのに、実行時の値を使っている」という意味です。C++の配列宣言int a[?];は、基本的にサイズがコンパイル時に決まる必要があります。ところが上の例では、RとCはキーボード入力(実行時)で決まるので、コンパイラは配列のサイズを事前に確定できません。

「constなら定数になるのでは?」でハマるポイント

混乱しやすいのが「constを付ければ定数扱いになるのでは?」という誤解です。

const int N = 10;
int a[N]; // これはOK(Nはコンパイル時に確定する)

一方、入力で決めた値はconstにしても定数式ではありません。

int N;
std::cin >> N;
const int M = N;
int a[M]; // これはダメ(Mは実行時に決まる)

要するに、「constかどうか」ではなく「コンパイル時に値が確定できるか」が本質です。

Dev-C++では動くのにMSVCでは動かない理由:VLAはC++標準外

int a[R][C];のように、実行時サイズで配列を宣言する仕組みは「可変長配列(VLA: Variable-Length Array)」と呼ばれます。これはC言語(C99以降)では言語機能として知られていますが、C++の標準仕様としては採用されていません。

Dev-C++はGCC系(MinGWなど)を使う構成が多く、GCCは拡張機能としてVLAを受け付けることがあります。そのため「たまたま動く」ように見えます。しかし、MSVCはVLAをサポートしない方針のため、C++として正しくないコードはコンパイルエラーになります。

ここで重要なのは、「MSVCが意地悪」なのではなく、MSVCの挙動の方が“標準C++寄り”であるという点です。移植性(他の環境でも同じように動く)を重視するなら、VLAに頼らない設計に寄せるのが正解です。

結論:実行時サイズの2次元データは「動的確保」で扱う

実行時にRとCが決まるなら、必要なのは「スタック上の固定配列」ではなく、実行時サイズに合わせて領域を確保できる仕組みです。C++では、代表的な選択肢が次の3つになります。

  • std::vector(推奨:安全・移植性・保守性が高い)
  • 1次元で確保して添字計算(推奨:高速・連続メモリでキャッシュに強い)
  • new/delete(可能だがミスしやすく、通常は避けたい)

ここからは、Visual Studioで確実に通る、実用的な実装を用途別に紹介します。

解決策A:std::vector<std::vector<int>>で素直に2次元を表現(安全で書きやすい)

まずは最も直感的な書き方です。行ごとにvectorを持つため、a[i][j]がそのまま使えます。

#include &lt;iostream&gt;
#include &lt;vector&gt;

int main() {
    int R, C;
    std::cin &gt;&gt; R &gt;&gt; C;

    std::vector&lt;std::vector&lt;int&gt;&gt; a(R, std::vector&lt;int&gt;(C));

    for (int i = 0; i &lt; R; ++i)
        for (int j = 0; j &lt; C; ++j)
            std::cin &gt;&gt; a[i][j];

    for (int i = 0; i &lt; R; ++i) {
        for (int j = 0; j &lt; C; ++j) std::cout &lt;&lt; "\t" &lt;&lt; a[i][j];
        std::cout &lt;&lt; "\n";
    }
}

この方法が向いているケース

  • とにかく分かりやすく書きたい
  • 行・列のサイズが入力で決まり、サイズ変更(行追加/列追加)もあり得る
  • 多少のオーバーヘッドより、安全性・読みやすさを優先したい

注意点:メモリが「行ごと」に分かれる(連続メモリではない)

vector<vector<int>>は、各行のvectorが別々の領域を持つことが多く、全体が1本の連続したメモリになりません。通常の入出力や表示では問題になりませんが、数値計算・画像処理・行列演算などでキャッシュ効率を最大化したい場合には、次の「1次元vector方式」が有利になることがあります。

解決策B:1次元vectorで確保し、i*C + jでアクセス(高速・連続メモリ)

2次元に見せたいだけなら、実体は1次元の連続メモリにして、添字計算で2次元アクセスを実現するのが定番です。連続メモリなのでキャッシュに乗りやすく、大きなデータを扱うほど差が出やすい傾向があります。

#include &lt;iostream&gt;
#include &lt;vector&gt;

int main() {
    int R, C;
    std::cin &gt;&gt; R &gt;&gt; C;

    std::vector&lt;int&gt; a(R * C);

    auto at = [&amp;](int i, int j) -&gt; int&amp; { return a[i * C + j]; };

    for (int i = 0; i &lt; R; ++i)
        for (int j = 0; j &lt; C; ++j)
            std::cin &gt;&gt; at(i, j);

    for (int i = 0; i &lt; R; ++i) {
        for (int j = 0; j &lt; C; ++j) std::cout &lt;&lt; "\t" &lt;&lt; at(i, j);
        std::cout &lt;&lt; "\n";
    }
}

この方法が向いているケース

  • データが大きい、または高速化が必要
  • 連続メモリが欲しい(外部ライブラリに渡す、SIMD/最適化しやすい)
  • 「実装は少し工夫しても良い」のでパフォーマンスを優先したい

読みやすさを上げる:小さな行列クラスに包む

添字計算を毎回書くのが気になるなら、薄いラッパー(クラス)を用意すると、保守性が一気に上がります。WordPress記事としても「実務でそのまま持っていける」形になります。

#include &lt;vector&gt;
#include &lt;stdexcept&gt;
#include &lt;cstddef&gt;

class MatrixInt {
public:
    MatrixInt(int r, int c) : R(r), C(c), data(static_cast&lt;std::size_t&gt;(r) * c) {
        if (r &lt;= 0 || c &lt;= 0) throw std::invalid_argument("RとCは正の値が必要です");
    }

    int&amp; operator()(int i, int j) {
        return data[static_cast&lt;std::size_t&gt;(i) * C + j];
    }

    const int&amp; operator()(int i, int j) const {
        return data[static_cast&lt;std::size_t&gt;(i) * C + j];
    }

    int rows() const { return R; }
    int cols() const { return C; }

private:
    int R, C;
    std::vector&lt;int&gt; data;
};

使う側は次のように書けます。

#include &lt;iostream&gt;

int main() {
    int R, C;
    std::cin &gt;&gt; R &gt;&gt; C;

    MatrixInt a(R, C);

    for (int i = 0; i &lt; a.rows(); ++i)
        for (int j = 0; j &lt; a.cols(); ++j)
            std::cin &gt;&gt; a(i, j);

    for (int i = 0; i &lt; a.rows(); ++i) {
        for (int j = 0; j &lt; a.cols(); ++j) std::cout &lt;&lt; "\t" &lt;&lt; a(i, j);
        std::cout &lt;&lt; "\n";
    }
}

この形なら「配列っぽい見た目」も保ちつつ、メモリは連続、しかも境界チェックを入れたい場合もクラス側で拡張できます。

解決策C:new[]で動的確保(動くがミスが起きやすい)

どうしてもnewで書きたい場合は、次のように確保して、最後に必ずdelete[]で解放します。ただし、例外や早期returnなどで解放が抜けやすく、コードが伸びるほど事故が増えます。

#include &lt;iostream&gt;

int main() {
    int R, C;
    std::cin &gt;&gt; R &gt;&gt; C;

    int* a = new int[R * C];

    auto at = [&](int i, int j) -&gt; int&amp; { return a[i * C + j]; };

    for (int i = 0; i &lt; R; ++i)
        for (int j = 0; j &lt; C; ++j)
            std::cin &gt;&gt; at(i, j);

    for (int i = 0; i &lt; R; ++i) {
        for (int j = 0; j &lt; C; ++j) std::cout &lt;&lt; "\t" &lt;&lt; at(i, j);
        std::cout &lt;&lt; "\n";
    }

    delete[] a; // 忘れるとメモリリーク
}

newを使うなら、最低限「自動解放」に寄せる(unique_ptr)

標準C++の範囲でも、std::unique_ptrを使えば「delete[]を書き忘れる」事故を減らせます。それでもvectorより扱いは難しいので、理由がない限りvectorの方が無難です。

#include &lt;iostream&gt;
#include &lt;memory&gt;

int main() {
    int R, C;
    std::cin &gt;&gt; R &gt;&gt; C;

    auto a = std::make_unique&lt;int[]&gt;(R * C);

    auto at = [&](int i, int j) -&gt; int&amp; { return a[i * C + j]; };

    for (int i = 0; i &lt; R; ++i)
        for (int j = 0; j &lt; C; ++j)
            std::cin &gt;&gt; at(i, j);

    for (int i = 0; i &lt; R; ++i) {
        for (int j = 0; j &lt; C; ++j) std::cout &lt;&lt; "\t" &lt;&lt; at(i, j);
        std::cout &lt;&lt; "\n";
    }

    // unique_ptrなので自動解放
}

どれを選ぶべき?用途別のおすすめ早見表

迷ったら、まずは「AかB」です。特に学習・業務・保守まで見据えるなら、vectorは鉄板です。

方法書きやすさ安全性メモリ配置速度の期待おすすめ度
std::vector<std::vector<int>>高い(a[i][j]が直感的)高い(自動解放)行ごとに分割されがち用途次第(多くの場面で十分)高(まずこれ)
std::vector<int> + i*C+j中(添字計算が必要)高い(自動解放)連続メモリ高(キャッシュ効率が良い)高(大規模・高速化向け)
new/delete中低(解放忘れ・例外で漏れる)連続メモリ中〜高(実装次第)低(特別な理由がある時のみ)
unique_ptr<int[]>中中〜高(自動解放)連続メモリ中〜高中(vectorが使えない理由がある場合)

「スタック」と「ヒープ」を意識すると納得しやすい

VLAでint a[R][C];をやりたい気持ちは分かりますが、もう一つ現実的な問題があります。ローカル配列は多くの場合スタックに置かれるため、サイズが大きいと簡単にスタックオーバーフローを起こします。たとえばR=5000、C=5000なら、intが4バイトとして約100MBです。これは多くの環境のスタック容量を超えます。

一方、vectorやnewは主にヒープ領域を使うため、(もちろん限界はありますが)巨大配列でも扱いやすくなります。つまり「実行時にサイズが変わる」だけでなく「サイズが大きくなり得る」という意味でも、動的確保に寄せるのが実務では安全です。

観点スタック(ローカル配列の典型)ヒープ(vector/newの典型)
確保・解放高速(スコープで自動)ややコストあり(ただし通常は許容)
容量の上限小さめ(環境依存で数MB〜)比較的大きい(メモリの空きに依存)
サイズ可変苦手(C++標準では不可)得意
安全性サイズミスで即クラッシュしやすいvectorは例外安全・自動解放

実装を「事故らせない」ためのチェックポイント

動的確保にした後も、実行時入力が入る以上、以下のポイントを押さえておくと品質が上がります。

RとCの入力チェック(負数・ゼロ・極端な値)

入力が負数だったりゼロだったりすると、確保サイズがおかしくなります。特にR * Cは符号付きintだとオーバーフローしやすいので注意が必要です。最低限、次のようなチェックを入れると堅牢です。

if (R &lt;= 0 || C &lt;= 0) {
    std::cerr &lt;&lt; "RとCは正の整数を入力してください\n";
    return 1;
}

R*Cのオーバーフローを意識する

例えばR=70000、C=70000なら、R*Cはintの範囲を簡単に超えます。vectorに渡すときはstd::size_tに変換して、必要なら「上限」を設けるのが現実的です。

std::size_t r = static_cast&lt;std::size_t&gt;(R);
std::size_t c = static_cast&lt;std::size_t&gt;(C);

if (r &gt; 100000 || c &gt; 100000) {
    std::cerr &lt;&lt; "サイズが大きすぎます\n";
    return 1;
}

std::vector&lt;int&gt; a(r * c);

上限値は用途次第ですが、「ユーザー入力をそのまま巨大確保しない」だけでも事故が減ります。

表示の整形はI/Oのボトルネックになりやすい

大量の要素をタブ区切りで出すと、計算よりも出力が遅くなることがよくあります。学習目的では問題ありませんが、性能評価をするなら「出力を消す」「要素数を減らす」などの工夫も必要です。vector方式の速度差を正しく見るためにも、I/Oの影響は意識しておくと良いです。

よくある疑問にまとめて回答

「MSVCでVLAを有効にする設定」はないの?

VLAはC++標準の機能ではないため、基本的に「設定で解決する」類のものではありません。拡張に依存するほど、環境が変わった時にビルドが壊れやすくなります。Visual Studioで安定して動かすなら、vector等に置き換えるのが最短です。

std::vector<std::vector<int>>は遅い?使ってはいけない?

「遅い」と断定はできません。多くのアプリでは十分速いです。ただし、連続メモリではないため、要素を順に走査するような処理(行列演算、畳み込み等)では、1次元vectorの方が有利になりやすい、という傾向はあります。迷ったら、まずAで正しく動くものを作り、必要になったらBに寄せる、が現実的です。

本当に“2次元配列”が必要?行列なら外部ライブラリも選択肢

学習や課題なら今回のvectorで十分ですが、実務で行列演算を多用するなら、行列専用ライブラリ(例:Eigenなど)を使うと、添字計算や最適化、API設計まで整っていて生産性が上がることがあります。とはいえ、基礎として「実行時サイズは動的確保で扱う」を押さえておけば、どのライブラリに移っても応用が利きます。

「どうしてもa[i][j]の書き味が欲しい」場合は?

素直にvector<vector<T>>を使うのが簡単です。連続メモリが欲しいのに書き味も欲しい場合は、先ほどのラッパークラスのようにoperator()を用意するのが現実解です。C++では「低コスト抽象化」がしやすいので、見た目と性能を両立できます。

まとめ:Visual StudioでC2131が出たら「VLAをやめてvectorへ」

Visual Studio(MSVC)でint a[R][C];がC2131: expression did not evaluate to a constantになるのは、可変長配列(VLA)がC++標準ではなく、MSVCがそれをサポートしないためです。解決策は「実行時サイズの領域」を使う設計に切り替えることです。

  • 手早く安全に直すなら:std::vector<std::vector<int>>
  • 性能や連続メモリを重視するなら:std::vector<int> + i*C+j
  • new/deleteは原則避け、使うならunique_ptr等で自動解放へ

この方針で書けば、Visual Studioでもコンパイルが通り、他環境へ移植しても壊れにくい「標準C++」として扱えます。エラーを潰すだけでなく、将来の保守や性能まで見据えて、用途に合った実装を選んでみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次