mots quotidiens.
Daichi Mochihashi (持橋大地) daichi <at> ism.ac.jp by hns, version 2.10-pl1.

先月 2024年04月 来月
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

2009年10月30日(金) [n年日記]

#1 C array/matrix with size

C言語では標準では matrix[7][20] のようにサイズが決まっている場合以外は 多次元配列は使えないが, 「1次元配列の配列」を作ると *1 matrix[i][j] のように使うことができるので, 配列の配列を確保する dmatrix.c というファイルを書いて便利に使っている。
これは
double **matrix = dmatrix(rows, cols);
for (i = 0; i < rows; i++)
        for (j = 0; j < cols; j++)
                if (matrix[i][j] > 0) ...
のように使う。ただし, 単なる配列の配列なので, 行列の大きさ(rowsとcols) は別に覚えておく必要がある。
C++を使ってよければ, STLで vector<double>->size() とすればよいが, C言語でも 配列や行列のサイズは別に持たず, 自分で覚えていてほしい, という場面は 非常に多いと思う。 実際, STLでも使っているのはほとんど size() メソッドばかりな気がします。

一番簡単な解決策は, ベクトルの場合は

typedef struct {
        double *content;
        int length;
} dvector;
のような構造体を定義しておいて
for (i = 0; i < v->length; i++)
        if (v->content[i]) ...
のように使うことですが, これは v[i] のようにはアクセスできません (contentは構造体の先頭にあるが, lengthフィールドがあるため, 無理矢理 v[i] としてもオフセットが狂ってしまう)。

少し考えると, 後ろに length があるからいけないのであって, 前に持たせればよくね? ということで, 次のようなコードを書くと, C言語でも普通に配列に長さを持たせる ことができることに気付いた。

/* dvector2.c */
double *
dvector (int size)
{
        double *v;
        
        if ((v = (double *)calloc(size + 1, sizeof(double)))
            == NULL)
        {
                perror("dvector");
                return NULL;
        }
        /* record size */
        *((int *)v) = size;
        v += 1;
        
        return v;
}

void
free_dvector (double *v)
{
        free(v - 1);
}
ここで, dvector.h で
#define vsize(v)        *((int *)(v-1))
としておくと,
double *v = dvector(20);
double **m = dmatrix(n, k);

for (i = 0; i < vsize(v); i++)
        if (v[i] > 0) ...
for (i = 0; i < mrows(m); i++)
        for (j = 0; j < mcols(m); j++)
                if (m[i][j] > 0) ...
のように普通にMATLABのように使うことができ, 長さを覚えておく必要がない。 行列の場合にも同じことができ, 2つ分前に行数と列数を入れておいて, *((int *)) でアクセスすればok。下に dvector2.{c,h}, dmatrix2.{c,h} を置いておきます。

ポイントは, これは全く合法的なことをしているので, C言語なら必ず使えて, 外からは普通の double *, double ** と全く同じだということです。
これまで, 配列のサイズを変更する時は前の長さを与えないといけなかったり, 行列の場合は free() する場合もfree()するべき列の数を外から与えて やらないといけませんでしたが, これで勝手に長さを自分で取得して リサイズしたり, free() したりできるので, その点でも便利です。

唯一心配すべきなのは, double や double * にintが入らない場合ということですが, sizeof(int) > sizeof(double) だったり, int がポインタのサイズより大きいような 変態的な環境は普通はないと思うので, 心配しなくてよさそうです (thanks to 高林君)。 *2
これで, C言語の配列/行列もMATLABのように使えるようになりました。

・ おまけ:

これが必要になったのは, 僕が書いたのでないプログラムに手を入れて いて, そこで #define matrix_val(m,i,j) m->content[i]->content[j] とか matrix_val(mu, n, m) = matrix_val(nu, n + 1, matrix_val(c, i + n, j)); のような書き方がされていて, 個人的に非常に読みにくかったからでした。
上の dmatrix を使えばこれは mu[n][m] = nu[n+1][c[i+n][j]] と短く書くことができ ますが, 最初perlで文字列置換でやろうとしたところ, 再帰的に行うのが難しいことに気付きました。
少し考えて, cpp を使うことに。次のようなファイル replace.cpp
#define matrix_val(m,i,j)       m[i][j]
#define vector_len(v)           vsize(v)
..
を用意しておいて,
#!/bin/sh
#
#    indenter
#
if [ $# -lt 2 ]; then
        echo "usage: $0 replace.cpp source.c"
        exit 0
fi
sed 's/^#/_CPP_DIRECTIVE_/g' $2 | cat $1 - | cpp -C -P | \
sed 's/^_CPP_DIRECTIVE_/#/g' | indent -bl -bli0 -psl -kr
のようなスクリプトを書くことで, 綺麗に変換することができました。


*1: ちなみに, 中身をまとめて1次元の配列として確保しても, うまく操作すると matrix[i][j] でアクセスできるという話を 2004年の日記 に書きましたが, これは上の配列の配列より遅くなります。
実際には配列の操作では比較的近い場所を続けてアクセスすることが多いので, 細かく分かれていた方がページフォルトが起こりにくい, というような理由もありそう です。
*2: これはポインタの操作を簡単にしているためにこうしているだけで, 本質的な問題ではありません。sizeof(double) * len + sizeof(int) を確保するように すれば, どんな場合でも原理的にokです。

1 days displayed.
タイトル一覧
カテゴリ分類
 なかのひと
Powered by hns-2.10-pl1, HyperNikkiSystem Project