配列で負の添え字を使う

    C言語やC++でシミュレーション等を行う際に、配列の添え字に負の数を使いたいことがあります。標準的には、C言語やC++では添え字は0以上となっていますが、ポインタを使えば負の添え字が実現できます。具体的には以下のようになります。
    int main(){
        double *a;
        double dummy[2001];
        a = dummy + 1000;
    
        /* これで a[-1000]からa[1000]が使える。*/
    
        return 0;
    }
    配列 dummy の1000番目の要素のアドレス(ポインタ)を a に設定しているわけです。

多次元配列の動的確保

    例えば、1000×1000の二次元配列を確保する場合に、1000回もメモリ確保するようなサンプルプログラムを見かけますが、特殊な用途を除いては、あまり勧められません。それほど大きくない配列なら、一括して確保した方が安心です。
    m×n の二次元配列を確保する例を示します。C言語だと以下のようになります。
    #include <stdlib.h>
    
    int main(){
        int m,n;
        int i;
        double **a;
        m = 1000;
        n = 1000;
        a = (double**)malloc(m*sizeof(double*));
        a[0] = (double*)malloc(m*n*sizeof(double));
        for (i=1; i<m; i++){
            a[i] = a[i-1] + n;
        }
    
        /* これで a[0][0] ~ a[m-1][n-1] が使える */
    
        /* 配列を使い終わったらメモリを解放する */
        free(a[0]);
        free(a);
    
        return 0;
    }
    C++だと以下のようになります。
    int main(){
        int m = 1000;
        int n = 1000;
        double **a = new double*[m];
        a[0] = new double[m*n];
        for (int i=1; i<m; i++){
            a[i] = a[i-1] + n;
        }
    
        // これで a[0][0] ~ a[m-1][n-1] が使える
    
        // 配列を使い終わったらメモリを解放する
        delete[] a[0];
        delete[] a;
    
        return 0;
    }
    ポインタを格納する配列をまず確保し、さらにデータを格納する領域を確保して先頭アドレス(ポインタ)をポインタ用の配列の最初の要素にセットします。後は順次、ポインタ用配列に値をセットしていきます。同様の方法で、3次以上の配列も動的確保することができます。

負の添え字を使える多次元配列の動的確保

    今まで述べた方法の複合で、負の添え字を使える多次元配列の動的確保も可能です。
    例として、a[-m][-n]~a[m][n] が使用可能な配列を確保する例を示します。C言語だと以下のようになります。
    #include <stdlib.h>
    
    int main(){
        int i;
        int m, n;
        double **a, **dummy1;
        double *dummy2;
        m = 1000;
        n = 1000;
        dummy1 = (double**)malloc((2*m+1)*sizeof(double*));
        dummy2 = (double*)malloc((2*m+1)*(2*n+1)*sizeof(double));
        dummy1[0] = dummy2 + n;
        for (i=1; i<=2*m+1; i++){
            dummy1[i] = dummy1[i-1] + 2*n + 1;
        }
        a = dummy1 + m;
    
        /* これで a[-m][-n] ~ a[m][n] が使える */
    
        /* 配列を使い終わったらメモリを解放する */
        free(dummy2);
        free(dummy1);
    
        return 0;
    }
    C++だと以下のようになります。
    int main(){
        int m = 1000;
        int n = 1000;
        double **dummy1 = new double*[2*m+1];
        double  *dummy2 = new double[(2*m+1)*(2*n+1)];
        dummy1[0] = dummy2 + n;
        for (int i=1; i<=2*m+1; i++){
            dummy1[i] = dummy1[i-1] + 2*n + 1;
        }
        double **a = dummy1 + m;
    
        // これで a[-m][-n] ~ a[m][n] が使える
    
        // 配列を使い終わったらメモリを解放する
        delete[] dummy2;
        delete[] dummy1;
    
        return 0;
    }

注意

    C言語やC++において、負の添え字というのは一般的には用いられませんので、使う際はコメントで説明を詳しく書くなどして、デバッグの際に混乱しないよう配慮しましょう。
    また、ポインタ変数が有効範囲外を指すような使い方もしないように気を付けましょう。例えば、
        double *a;
        double b[10];
        a = b - 1;
    と書くと、a[1]~a[10] で b[0]~b[9] にアクセスできそうですが、 ポインタ変数 a が有効範囲外(配列 b の開始アドレスより小さいアドレス)を指していますので、正しい使い方ではありません(問題なく動く場合も多いでしょうけど、保証はできません)。今回、上で紹介した例については、その点は大丈夫です。
    もう少し細かいことを言うと、ポインタの有効範囲は、配列の最初の要素のアドレスから、配列の最後の要素を1つ超えた要素のアドレスまでです(最後の要素までではない)。よって、例えば、
        double a[10];
        double *p;
        int i;
        p = a;
        for(i=0; i<10; i++){
            *p = i;
            p++;
        }
    というようなプログラムを考えると、ポインタ変数 p の値はループの最後の実行で配列 a の最後の要素のアドレスを超えてしまいますが、一つ超えただけですので、これは正しいプログラムであるということになります。もちろん、ポインタ変数の取る値として有効というだけですので、配列の範囲外のアドレスにアクセスしてはいけません。