質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.48%

  • C

    3830questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • ポインタ

    113questions

    ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

C 入れ子構造の構造体にて配列を可変長にしたい

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 2,489

DD51_742

score 5

前提・実現したいこと

現在Cの入れ子の構造体があり、構造体の中での配列を固定長から可変長へ変更したい
仕様変更があります。

可変長化するにあたり、下記の制約があります。

1.現在静的メモリをmallocでヒープ領域に確保
2.入れ子の構造体の領域は、使用している独自処理のため、連続したメモリ領域である必要がある。
3.可変にするのはREC_SIZEのdefine部分のみで良い。

該当のソースコード

#define REC_SIZE 100000

struct head {
  int size;
  char ver[10];
};

struct data {
  struct dada_rec {
    int no;
    char name[30];
  } rec[REC_SIZE];
};

struct raw {
  int value[3][4][REC_SIZE];
};

struct data_all {
  struct head head;
  struct data data;
  struct raw raw;
};

struct data_all dd;

int main(int argc, char **argv) {
 // dd の領域をヒープで、構造体内の配列を可変長に
}

試したこと

  1. struct data_all内の各構造体を、ポインタに変更
  2. struct data_allの使用する領域長を自前で計算して、その領域長にてmallocをして
    unsigind charの配列に代入
  3. 各構造体のポインタにキャスト(キャストする際には各構造体のサイズを加味して、自前でポインタ計算して各構造体の先頭のアドレスが割付くように代入。

 困っていること

可変長にする部分の3次元配列部分をアクセスすると、セグメンテーションフォルトが発生する。

補足情報(言語/FW/ツール等のバージョンなど)

Cygwin64 gcc4.9です。

サンプルのソースは、擬似コードです。仕事のソースのため守秘義務のため持ち出し不可のため。
尚現在は自宅でCygwin64 gcc5.3 の環境で確認可能な状態です。

10年近くCを触っていないのいきなり3次元配列が出てきて、苦労しています。

皆様方の知恵を拝借したいと思います。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • MIURA_Yasuyuki

    2016/04/29 13:21

    可変長にする配列は動的に長さを変更できる必要があるのでしょうか?

    キャンセル

  • DD51_742

    2016/04/29 22:30

    動的に長さを変更可能にする必要があります。

    キャンセル

  • MIURA_Yasuyuki

    2016/04/30 10:05

    回答ありがとうございます。少し考えてみます。良さそうな案を思いついたら投稿させていただきます。

    キャンセル

  • HogeAnimalLover

    2016/05/07 02:15

    多次元配列の一部をポインタで代用すると不自然になるよ。(できないことはないけど)メモリの連続性が意図通りになっていないのかもしれないよ。 T a[X][Y]; → {T1 T2 ・・・TX}1、{T1 T2・・・TX}2{T1 T2 ・・・TX}Y →要素数 XY、バイト数 XYsizeof(T) T* b[X];//この後各要素にデータを割り当てる → {T*1 T*2 ・・・T*X} →要素数 X、バイト数Xsizeof(T*) これが複雑になって意図通りになっていないのでは?

    キャンセル

回答 5

checkベストアンサー

+1

頑張って、たぶん、こうしたいんだろうってのを実装してみました。

下記の機能を使っており、gcc(GCC 5.3.0)とclang(LLVM 7.3.0)でしか試していません。
Zero Length - Using the GNU Compiler Collection (GCC)
ただ、下記の情報もあるので、Visual C++でもいけると思います。
構造体の可変長配列

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

struct head {
    int size;
    char ver[10];
};

// callocするときにsizeofの情報が必要なので外で定義
struct dada_rec {
    int no;
    char name[30];
};

struct data {
    int size; // 0サイズ構造体が作れないので、ダミーでsizeを追加
    struct dada_rec rec[]; // 可変にできるのは最後だけ、[]は0サイズ
};

struct raw {
    int size; // 0サイズ構造体が作れないので、ダミーでsizeを追加
    int value[][3][4]; // 可変にできるのは最初の[]だけ、int[3][4]型の可変
};

struct data_all {
    struct head head;
    struct data *data; // 可変な構造体を途中に書くことはできない
    struct raw raw; // 最後の場合は可能
};

struct data_all *dd; // 可変部分があるため静的に作ることはできない

int main(int argc, char *argv[])
{
    int rec_size = 100000;

    // dd の領域をヒープで、構造体内の配列を可変長に
    // 0埋めコード書くのが面倒なのでcalloc
    dd = (struct data_all *)calloc(1,
            sizeof(struct data_all) + sizeof(int[3][4]) * rec_size);
    dd->raw.size = rec_size;

    // dataの領域を作成
    dd->data = (struct data *)calloc(
            1, sizeof(struct data) +
                       sizeof(struct dada_rec) * rec_size);
    dd->data->size = rec_size;

    // ダミーで値を入れてみる
    for (int i = 0; i < dd->data->size; i++) {
        dd->data->rec[i].no = i;
        sprintf(dd->data->rec[i].name, "%08d", i);
    }
    for (int i = 0; i < dd->raw.size; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 4; k++) {
                dd->raw.value[i][j][k] = i * (j + 1) - k;
            }
        }
    }
    printf("%d: %s\n", dd->data->rec[3].no, dd->data->rec[3].name);
    printf("%d: %s\n", dd->data->rec[99999].no, dd->data->rec[99999].name);
    printf("%d\n", dd->raw.value[0][0][0]);
    printf("%d\n", dd->raw.value[42][1][1]);
    printf("%d\n", dd->raw.value[99999][2][3]);
    return 0;
}


※ エラー処理とかは省いていますので、ご注意を。

Q&A
Q: []でいいの?
A: GCCとVCでは0サイズと見なして大丈夫のようです。ただ、それ一つだけだと構造体全体が0サイズになるため、他に何か必要になります。[1]とする書き方もあるようですが、正式なドキュメントが見つけられませんでした。
Q: int value[3][4][]とはできないの?
A: できません。int value[3][4][]は「intが可変個ある型が4個ある型が3個ある型」となり、可変個部分が確定しないと最終的にアクセスできません。なので、int value[][3][4]として、「intが4個ある型が3個ある型が可変個ある型」にします。int[3][4]が可変個ある形になるので、いくつあるかわからなくても、n番目にアクセスできます。
Q: 可変長部分は最後以外には書けないの?
A: 書けません。メモリを確保できても、途中に可変長部分があるとがそこがどこで終わるのかわからなくなるからです。
Q: 可変長配列(VLA)ってこれのこと?
A: 違います。全く別の機能です。
Q: 構造体可変長配列(VLAIS)ってこれのこと?
A: 違います。これまた別の機能です。
Q: これってCの標準仕様?
A: だと思ったけど、調べ切れてないです。わかる人は教えてください。


2.入れ子の構造体の領域は、使用している独自処理のため、連続したメモリ領域である必要がある。

という制限があったのですね。struct data_allの構造を変えずにするとなると、普通に構造体や配列としてアクセスする方法では無理です。メモリを自分で確保して、直接計算してアクセスするしかないと思います。ということで、書き直しました。

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

struct head {
    int size;
    char ver[10];
};

struct data {
    int no;
    char name[30];
};

struct data_all {
    int size;
    size_t mem_size;
    void *mem;
};

struct data_all data_all_create(int size)
{
    assert(0 <= size);
    struct data_all dd;
    dd.size = size;
    dd.mem_size = sizeof(struct head) + sizeof(struct data) * size +
            sizeof(int) * 3 * 4 * size;
    assert(0 <= dd.mem_size);
    dd.mem = calloc(1, dd.mem_size);
    return dd;
}
void data_all_destroy(struct data_all dd)
{
    free(dd.mem);
    dd.mem = NULL;
}

struct head *data_all_head(struct data_all dd)
{
    assert(dd.mem != NULL);
    return (struct head *)dd.mem;
}
struct data *data_all_data(struct data_all dd, int n)
{
    assert(dd.mem != NULL);
    assert(0 <= n && n < dd.size);
    return (struct data *)(dd.mem + sizeof(struct head) +
            sizeof(struct data) * n);
}
// data[y][x][n]のこと
int *data_all_value(struct data_all dd, int n, int x, int y)
{
    assert(dd.mem != NULL);
    assert(0 <= n && n < dd.size);
    assert(0 <= x && x < 4);
    assert(0 <= y && y < 3);
    size_t forward = sizeof(struct head) + sizeof(struct data) * dd.size +
            sizeof(int) * (n + dd.size * x + dd.size * 4 * y);
    assert(forward < dd.mem_size);
    return (int *)(dd.mem + forward);
}
int data_all_get_value(struct data_all dd, int n, int x, int y)
{
    return *data_all_value(dd, n, x, y);
}
void data_all_set_value(struct data_all dd, int n, int x, int y, int value)
{
    *data_all_value(dd, n, x, y) = value;
}

struct data_all dd; // 可変部分があるため静的に作ることはできない

int main(int argc, char *argv[])
{
    int rec_size = 100000;
    dd = data_all_create(rec_size);
    if (dd.mem == NULL) {
        fprintf(stderr, "Failed to allocate memory.\n");
        return 1;
    }
    // 既にあるデータ構造を入れる場合は、次のようにする。
    // memcpy(dd.mem, ○○, dd.mem_size);

    // ダミーで値を入れてみる
    for (int n = 0; n < dd.size; n++) {
        struct data *d = data_all_data(dd, n);
        d->no = n;
        sprintf(d->name, "%08d", n);
    }
    for (int n = 0; n < dd.size; n++) {
        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 3; y++) {
                // data[y][x][n] = ...
                data_all_set_value(
                        dd, n, x, y, n * (y + 1) - x);
            }
        }
    }
    printf("%d: %s\n", data_all_data(dd, 3)->no,
            data_all_data(dd, 3)->name);
    printf("%d: %s\n", data_all_data(dd, 99999)->no,
            data_all_data(dd, 99999)->name);
    printf("%d\n", data_all_get_value(dd, 0, 0, 0)); // 最小値
    printf("%d\n", data_all_get_value(dd, 42, 1, 1));
    printf("%d\n", data_all_get_value(dd, 99999, 3, 2)); // 最大値
    data_all_destroy(dd);
    return 0;
}


C++でクラス作っているような感じで実装すればできるかなと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/06 23:15

    サンプルの提示ありがとうございます。
    私のやりたい事にほぼ近いです。

    家の環境で実行するとSegmentation faultが発生しました。
    x86_64 Cygwin 2.5.1
    gcc (GCC) 5.3.0

    以下gdb で実行して停止した箇所です。
    (gdb) run
    Starting program: /home/murakami/sample/sample.exe
    [New Thread 15856.0x3f30]
    [New Thread 15856.0x3c14]
    [New Thread 15856.0x3fc0]
    [New Thread 15856.0x3b2c]
    3: 00000003
    99999: 00099999
    0
    83

    Program received signal SIGSEGV, Segmentation fault.
    0x00000001004012aa in data_all_get_value (dd=..., n=99999, x=2, y=3)
    at sample.c:52
    52 return *data_all_value(dd, n, x, y);
    (gdb) p dd
    $1 = {size = 100000, mem_size = 8400016, mem = 0x6ffff7f0010}
    (gdb) p n
    $2 = 99999
    (gdb) p x
    $3 = 2
    (gdb) py

    自分でも調べて見ますが、何かわかりそうであればアドバイス頂ければ幸いです

    キャンセル

  • 2016/05/07 00:10 編集

    すいません、バグがあったので修正しています。
    1. data_all_value()でsizeof(struct data)分を足していなかったです。
    2. 一番最後はdata_all_get_value(dd, 99999, 3, 2)でした。
    その他、assertを追加したので境界値計算がおかしかったらassertで検知できると思います。また、sturct data_allのmem_sizeはより正確にsize_tにしました。そういえば、配列の大きさはintにしてしまいましたけど、intでよかったのでしょうか?

    キャンセル

+1

C言語ということで、よろしいでしょうかね、、、
可変長構造体は、ちょっとトリッキーな方法で確保することとなります。

※ただし、先にかきますが、これで可変確保できるのは、headとdataだけとなります。

定義

typedef struct _data {
  char a;
  long b;
  unsigned char c;
  int sample[1];
} data ;


と、とりあえず1の長さの配列を定義しておいて、mallocの段階で、

data* p = malloc((sizeof(data) - sizeof(int)) + sizeof(int) * NUM_REC);


のようにメモリ確保を行います。
mallocの引数の詳細は、

(sizeof(data) - sizeof(int))


の部分で、

int sample[1]


以外の構造体のサイズを計算し、

sizeof(int) * NUM_REC


で、NUM_REC文の、intを確保することとなるので、結果的に、

typedef struct _data {
  char a;
  long b;
  unsigned char c;
  int sample[NUM_REC];
} data ;


を確保したこととなって、可変長の構造体mallocができるという動作となります。
まあ、要するに、 int sample[1];としておいて、コンパイラ側を、だましておいて、実際には、それよりも長く確保しているという状態となります。

sizeof(int)の部分のintは、どのような型にでもなりますので、スレ主様の提示の、それぞれの型をこうりこめばよろしいかと。(int[3][4]とか、struct dada_recとか)。

ただ、この方法では、もうお気づきかと思いますが、
data_all
を可変に確保することは不可能です。あくまでも、最後の要素のみが、可変にできるだけですので、、、。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

こんにちは。

ご存知のようにC/C++は原則として可変長配列をサポートしていません。
例外的にCではスタック上に確保する場合に限りサポートしているようです。(VLA)
gccはそれをもう少し拡張拡張して構造体内の配列を可変長にできるようです(VLAIS)が、やはりスタック上に確保した例しか見当たりません。

旧来からよく使われる長さ1の配列を構造体の最後に配置するテクニックを使うしかないように思います。(ItoTomonoriさんが回答されている方法です。)
rawを適切な位置へ配置することはできませんが、C/C++言語は静的型付け言語ですので、これは不可能と思います。rawの位置が動的に変化しますので、動的に処理するしかありません。


【追記】
rawは3次元配列で、しかも、最下位の次元を可変長にしたいのですか...
C/C++ではそれも不可能です。例えば、value[0][1][0]のアドレスを静的に決定できませんので。
静的型付け言語で、変数領域を連続領域に「動的」に割り当てすることは不可能と思います。

不連続な領域、もしくは、ポインタ経由するような方法でならC/C++も含めて対応できる言語は多いと思いますが、連続領域に直接動的に割り当てることが可能な言語は少ないだろうと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/06 23:19

    3次元配列の可変長配列について、アドバイスありがとうございます。
    ここ10年程C系列の言語はC++しか触っていないので、C99の可変長配列はネット上に散らばる断片的な情報でしか知りませんでした。

    個人的には配列は2次元までしか使わないのが、普通かなと思っていましたが既存ソースコードが3次元配列を使用しており、使用するデータも3次元配列に依存する形式なので、困っておりました。

    キャンセル

0

2.入れ子の構造体の領域は、使用している独自処理のため、連続したメモリ領域である必要がある。 

もしその構造体がdata_all構造体のことを指しているのであれば、それは無理です。

とりあえず、それぞれのデータ領域が連続していればよいのであれば、このような感じでできます。

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

struct head
{
    int size;
    char ver[10];
};

struct data
{
    struct dada_rec
    {
        int no;
        char name[30];
    } *rec;        // data構造体の中にはこの変数しかないので、これを動的に確保
};

struct raw
{
    int *value[3][4];    // 別途確保した領域を指すポインタの二次元配列
};

struct data_all
{
    struct head head;
    struct data data;
    struct raw raw;
};

struct data_all dd;

int main()
{
    int rec_size = 100000;

    // dd.data.recは普通にメモリ確保
    dd.data.rec = (struct dada_rec *)malloc(rec_size * sizeof(struct dada_rec));

    // dd.raw.valueは、まず連続した大きな領域を確保して
    int *raw_data = (int *)malloc(3 * 4 * rec_size * sizeof(int));
    // 二次元配列に当該領域のポインタを格納
    for(int i = 0; i < 3; i++)
    {
        for(int j = 0; j < 4; j++)
        {
            dd.raw.value[i][j] = &raw_data[(i * 4 + j) * rec_size];
        }
    }

    // データが入れられることを確認

    for(int i = 0; i < rec_size; i++)
    {
        dd.data.rec[i].no;
        sprintf(dd.data.rec[i].name, "i=%d", i);
    }

    for(int i = 0; i < 3; i++)
    {
        for(int j = 0; j < 4; j++)
        {
            for(int k = 0; k < rec_size; k++)
            {
                dd.raw.value[i][j][k] = i + j + k;
            }
        }
    }

    // いらなくなったら領域解放
    free(dd.data.rec);
    free(dd.raw.value[0][0]);

    return 0;
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/06 23:23

    返信遅れましたが、3次元配列にポインタを格納する処理が参考になりました。

    キャンセル

0

配列が可変になった時点で構造体としては管理できなくなりますので、
その部分を外し、ポインタに置き換え、
別途確保したメモリにポインタを割り当てて利用することになるかと思います。

int rec_size=100000;

struct head { 
  int size; 
  char ver[10]; 
};

struct data_rec { 
    int no; 
    char name[30]; 
};

struct data_all { 
  struct head *head; 
  struct data_rec *data; 
  int *value[3][4]; 
};

struct data_all dd;
void *mem=malloc(sizeof(head) + sizeof(data_rec) * rec_size + sizeof(int) * 3 * 4 * rec_size);

dd.head = mem;
dd.data = dd.head + sizeof(head);
int *p  = dd.data + sizeof(data_rec) * rec_size;
for(int i = 0; i < 3; i++ ){
    for(int j = 0; j < 4; j++ ){
        dd.value[i][j] = p;
        p++;
    }
}


データの実体は、ddではなくmemになります。
(ddにはポインタしか入っていません)

追記:
現行プログラムの変数の影響

dd.head.sizedd.head->size
dd.head.verdd.head->ver
dd.data.data_rec[n].nodd.data[n].no
dd.data.data_rec[n].namedd.data[n].name
dd.raw.value[n][m] → dd.value[n][m]

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/04/29 17:07

    catsforepawさんとほぼカブってますね。
    失礼しました。

    キャンセル

  • 2016/05/06 23:22

    3次元配列の初期化方法参考になりました。
    今までの経験で3次元配列は、1回位しかないので記憶から完全に消えていました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.48%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C

    3830questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • ポインタ

    113questions

    ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。