ポインタとメモリ

C・C++の多次元配列

このレッスンでは、2次元の行列をたどり、行ごとの要素の合計を求めます。

はじめに

2次元配列は、行と列からなる表のようにイメージできます。CでもC++でも、2次元配列はメモリ上に行ごとの順番(row-major)で連続して保存されます。つまり、1行目がまるごと先に保存され、その後に2行目が続きます。

大切なポイント

  • ポインタは、まだ存在している正しい型のオブジェクトを指すか、nullにしておきましょう。
  • 確保したメモリ領域は、ちょうど1回だけ解放しましょう。
  • どの関数がメモリの持ち主(ownership)で、どのデータが読み取り専用(const)かをはっきりさせましょう。

動きで理解する

セルを指すと、その行と列、そしてメモリ上の位置がわかります。メモリには行が順番に並んで保存されています。

CとC++のコード例

C
実行 →
main.c
#include <stdio.h>

int main(void) {
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    for (int row = 0; row < 2; ++row) {
        int total = 0;
        for (int column = 0; column < 3; ++column) total += matrix[row][column];
        printf("row %d=%d\n", row, total);
    }
    return 0;
}
実行結果
row 0=6
row 1=15
C++
実行 →
main.cpp
#include <array>
#include <iostream>

int main() {
    std::array<std::array<int, 3>, 2> matrix{{{{1, 2, 3}}, {{4, 5, 6}}}};
    for (std::size_t row = 0; row < matrix.size(); ++row) {
        int total = 0;
        for (int value : matrix[row]) total += value;
        std::cout << "row " << row << '=' << total << '\n';
    }
}
実行結果
row 0=6
row 1=15

CとC++の比較

入れ子になった2つのループが行列をたどり、外側のループは各行を、内側のループはその行の要素を変数totalに足します。Cで2次元配列を関数に渡すときは、最初の次元以外のすべての次元の大きさを書く必要があります。C++のstd::arrayは大きさを型そのものに持ち、インデックスが有効かを確認するat()メソッドがあります。

C

メモリの持ち主と解放は、すべてプログラマーが自分で保証します。

C++

コンテナとRAIIが、オブジェクトの寿命が終わるときにリソースを自動で解放します。

発展問題

両方のバージョンを実行し、コードを書き換えて、それぞれの言語がもたらす安全性の違いを観察しましょう。

  • nullポインタやメモリ確保の失敗で試しましょう。
  • AddressSanitizerとUndefinedBehaviorSanitizerを付けて実行し、メモリのエラーを見つけましょう。
  • どのポインタがメモリの持ち主で、どのポインタが読むだけかをはっきり書き添えましょう。

チャレンジで練習する

このレッスンの内容を使って実際の問題を解きましょう。各チャレンジはブラウザで動き、すぐにテストされ、AI のレビューも受けられます。

すべてのチャレンジ