zirzirrrrr’s diary

日常の蓄積とかなんか

C言語で構造体の2次元配列を動的に割り当てる方法

はじめに

まずはじめに,構造体の1次元配列を動的に割り当てる方法を説明する. そして構造体の2次元配列を動的に割り当てる方法を3つ説明する.

なお,説明用のプログラムは全てコピペすれば動くようにするため各章は長くなっている.

また配列が確保できなかった場合等のエラー処理は一切考慮していない事もここに明記する.

構造体の1次元配列を動的に割り当てる

特にひねりなし

#include <stdio.h>
#include <stdlib.h>

#define NUMBER_PEOPLE 100

typedef struct {
  int number;
  double height;
  double weight;
} student_t;

int main(void)
{
  int i = 0;
  student_t *s;

  s = malloc(sizeof(student_t *) * NUMBER_PEOPLE);

  for (i = 0; i < NUMBER_PEOPLE; i++) {
    s[i].number = i;
    s[i].height = 150.0 + 0.1 * i;
    s[i].weight = 50.0  + 0.1 * i;

    printf("number: %3d height: %3.1f weight: %3.1f\n", s[i].number, s[i].height, s[i].weight);
  }

  free(s);

  return 0;
}

int型やdouble型等の配列の動的割り当てと違いはあまり無い.

構造体の二次元配列を動的に割り当てる

3つ割り当て方を紹介する.

多分先に紹介するものほど直感的にわかりやすく,後に紹介するものほどメモリの使用量やアクセス速度の面で有利である.

縦を確保して横を確保する.横を確保して縦を確保するとも言う.

素晴らしくセンスのない書き足しを上のプログラムに行った.

身長 248.01cm で体重 148.01kg とか意味わからないですね.

#include <stdio.h>
#include <stdlib.h>

#define NUMBER_PEOPLE 100

typedef struct {
  int number;
  double height;
  double weight;
} student_t;

int main(void)
{
  int i = 0, j = 0;
  student_t **s;

  s = malloc(sizeof(student_t *) * NUMBER_PEOPLE);

  for (i = 0; i < NUMBER_PEOPLE; i++) {
    s[i] = malloc(sizeof(student_t) * NUMBER_PEOPLE);
  }

  for (i = 0; i < NUMBER_PEOPLE; i++) {
    for (j = 0; j < NUMBER_PEOPLE; j++) {
      s[i][j].number = i * j;
      s[i][j].height = 150.0 + 0.01 * i * j;
      s[i][j].weight = 50.0  + 0.01 * i * j;

      printf("number: %4d height: %4.2f weight: %4.2f\n", s[i][j].number, s[i][j].height, s[i][j].weight);
    }
  }

  for (i = 0; i < NUMBER_PEOPLE; i++) {
    free(s[i]);
  }

  free(s);

  return 0;
}

これだと NUMBER_PEOPLE 回ごとにアドレスが途切れてしまうため速度を突き詰めたい時には改善しなくてはならない.

一気に全部確保して,各行もしくは列の先頭のアドレスを知らせる.

最初に NUMBER_PEOPLE * NUMBER_PEOPLE 個の領域を確保しておくことで NUMBER_PEOPLE 回ごとにアドレスが途切れてしまう問題を改善する.

#include <stdio.h>
#include <stdlib.h>

#define NUMBER_PEOPLE 100

typedef struct {
  int number;
  double height;
  double weight;
} student_t;

int main(void)
{
  int i = 0, j = 0;
  student_t *base_s, **s;

  base_s = malloc(sizeof(student_t)   * NUMBER_PEOPLE * NUMBER_PEOPLE);
  s      = malloc(sizeof(student_t *) * NUMBER_PEOPLE);

  for (i = 0; i < NUMBER_PEOPLE; i++) {
    s[i] = base_s + i * NUMBER_PEOPLE;
  }

  for (i = 0; i < NUMBER_PEOPLE; i++) {
    for (j = 0; j < NUMBER_PEOPLE; j++) {
      s[i][j].number = i * j;
      s[i][j].height = 150.0 + 0.01 * i * j;
      s[i][j].weight = 50.0  + 0.01 * i * j;

      printf("number: %4d height: %4.2f weight: %4.2f\n", s[i][j].number, s[i][j].height, s[i][j].weight);
    }
  }

  free(base_s);
  free(s);

  return 0;
}

これで連続した領域が確保できた.速度が上がる場合がある.

とは言え本質的に確保したいメモリの量は NUMBER_PEOPLE * NUMBER_PEOPLE であるが, 今回の方法はそれよりも多くメモリを確保している.

そこで最後に次に示す方法を紹介する.

1次元配列で確保して,アクセスする時に頑張って添字を掛け算する.

ヒャッハー!!2次元配列と言っても添え字の所で工夫すれば1次元配列でも問題ねえんだよ!!

脳筋みたいだが,実際これが一番使用するメモリが少なくて連続した領域が確保できるのである.

#include <stdio.h>
#include <stdlib.h>

#define NUMBER_PEOPLE 100

typedef struct {
  int number;
  double height;
  double weight;
} student_t;

int main(void)
{
  int i = 0, j = 0;
  int index = 0;
  student_t *s;

  s = malloc(sizeof(student_t) * NUMBER_PEOPLE * NUMBER_PEOPLE);

  for (i = 0; i < NUMBER_PEOPLE; i++) {
    for (j = 0; j < NUMBER_PEOPLE; j++) {
      index = i * NUMBER_PEOPLE + j;

      s[index].number = i * j;
      s[index].height = 150.0 + 0.01 * i * j;
      s[index].weight = 50.0  + 0.01 * i * j;

      printf("number: %4d height: %4.2f weight: %4.2f\n",
             s[index].number, s[index].height, s[index].weight);
    }
  }

  free(s);

  return 0;
}

アクセスしようとすると添字の計算の所でちょっと悩むが, 配列を確保する時は1次元配列の確保であるため細かいことを考えること無く確保出来る.

おわりに

この記事ではまずはじめにC言語で構造体の1次元配列を動的に割り当てる方法を紹介した.

そして,2次元配列を動的に割り当てる方法を3つ紹介した.

個人的には2番目に紹介した方法が配列へのアクセスのやりやすさと速度面のバランス的に良いのではないかと思う.

なお,だいたい4次元以上の配列が必要になる時は3番目の方法でないと

ポインタの → ポインタの → …… → ポインタの中身

のようになってしまうので大変速度面が悲しいことになってしまう(そもそもそんなに高次元の配列を使用しなければならないのはおかしいのでは無いのだろうか?とか思うことが必要かもしれない)

参考文献