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 <iostream>
#include <vector>
int main() {
int R, C;
std::cin >> R >> C;
std::vector<std::vector<int>> a(R, std::vector<int>(C));
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";
}
}
この方法が向いているケース
- とにかく分かりやすく書きたい
- 行・列のサイズが入力で決まり、サイズ変更(行追加/列追加)もあり得る
- 多少のオーバーヘッドより、安全性・読みやすさを優先したい
注意点:メモリが「行ごと」に分かれる(連続メモリではない)
vector<vector<int>>は、各行のvectorが別々の領域を持つことが多く、全体が1本の連続したメモリになりません。通常の入出力や表示では問題になりませんが、数値計算・画像処理・行列演算などでキャッシュ効率を最大化したい場合には、次の「1次元vector方式」が有利になることがあります。
解決策B:1次元vectorで確保し、i*C + jでアクセス(高速・連続メモリ)
2次元に見せたいだけなら、実体は1次元の連続メモリにして、添字計算で2次元アクセスを実現するのが定番です。連続メモリなのでキャッシュに乗りやすく、大きなデータを扱うほど差が出やすい傾向があります。
#include <iostream>
#include <vector>
int main() {
int R, C;
std::cin >> R >> C;
std::vector<int> a(R * C);
auto at = [&](int i, int j) -> int& { return a[i * C + j]; };
for (int i = 0; i < R; ++i)
for (int j = 0; j < C; ++j)
std::cin >> at(i, j);
for (int i = 0; i < R; ++i) {
for (int j = 0; j < C; ++j) std::cout << "\t" << at(i, j);
std::cout << "\n";
}
}
この方法が向いているケース
- データが大きい、または高速化が必要
- 連続メモリが欲しい(外部ライブラリに渡す、SIMD/最適化しやすい)
- 「実装は少し工夫しても良い」のでパフォーマンスを優先したい
読みやすさを上げる:小さな行列クラスに包む
添字計算を毎回書くのが気になるなら、薄いラッパー(クラス)を用意すると、保守性が一気に上がります。WordPress記事としても「実務でそのまま持っていける」形になります。
#include <vector>
#include <stdexcept>
#include <cstddef>
class MatrixInt {
public:
MatrixInt(int r, int c) : R(r), C(c), data(static_cast<std::size_t>(r) * c) {
if (r <= 0 || c <= 0) throw std::invalid_argument("RとCは正の値が必要です");
}
int& operator()(int i, int j) {
return data[static_cast<std::size_t>(i) * C + j];
}
const int& operator()(int i, int j) const {
return data[static_cast<std::size_t>(i) * C + j];
}
int rows() const { return R; }
int cols() const { return C; }
private:
int R, C;
std::vector<int> data;
};
使う側は次のように書けます。
#include <iostream>
int main() {
int R, C;
std::cin >> R >> C;
MatrixInt a(R, C);
for (int i = 0; i < a.rows(); ++i)
for (int j = 0; j < a.cols(); ++j)
std::cin >> a(i, j);
for (int i = 0; i < a.rows(); ++i) {
for (int j = 0; j < a.cols(); ++j) std::cout << "\t" << a(i, j);
std::cout << "\n";
}
}
この形なら「配列っぽい見た目」も保ちつつ、メモリは連続、しかも境界チェックを入れたい場合もクラス側で拡張できます。
解決策C:new[]で動的確保(動くがミスが起きやすい)
どうしてもnewで書きたい場合は、次のように確保して、最後に必ずdelete[]で解放します。ただし、例外や早期returnなどで解放が抜けやすく、コードが伸びるほど事故が増えます。
#include <iostream>
int main() {
int R, C;
std::cin >> R >> C;
int* a = new int[R * C];
auto at = [&](int i, int j) -> int& { return a[i * C + j]; };
for (int i = 0; i < R; ++i)
for (int j = 0; j < C; ++j)
std::cin >> at(i, j);
for (int i = 0; i < R; ++i) {
for (int j = 0; j < C; ++j) std::cout << "\t" << at(i, j);
std::cout << "\n";
}
delete[] a; // 忘れるとメモリリーク
}
newを使うなら、最低限「自動解放」に寄せる(unique_ptr)
標準C++の範囲でも、std::unique_ptrを使えば「delete[]を書き忘れる」事故を減らせます。それでもvectorより扱いは難しいので、理由がない限りvectorの方が無難です。
#include <iostream>
#include <memory>
int main() {
int R, C;
std::cin >> R >> C;
auto a = std::make_unique<int[]>(R * C);
auto at = [&](int i, int j) -> int& { return a[i * C + j]; };
for (int i = 0; i < R; ++i)
for (int j = 0; j < C; ++j)
std::cin >> at(i, j);
for (int i = 0; i < R; ++i) {
for (int j = 0; j < C; ++j) std::cout << "\t" << at(i, j);
std::cout << "\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 <= 0 || C <= 0) {
std::cerr << "RとCは正の整数を入力してください\n";
return 1;
}
R*Cのオーバーフローを意識する
例えばR=70000、C=70000なら、R*Cはintの範囲を簡単に超えます。vectorに渡すときはstd::size_tに変換して、必要なら「上限」を設けるのが現実的です。
std::size_t r = static_cast<std::size_t>(R);
std::size_t c = static_cast<std::size_t>(C);
if (r > 100000 || c > 100000) {
std::cerr << "サイズが大きすぎます\n";
return 1;
}
std::vector<int> 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++」として扱えます。エラーを潰すだけでなく、将来の保守や性能まで見据えて、用途に合った実装を選んでみてください。

コメント