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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C

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

ポインタ

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

Q&A

解決済

5回答

9471閲覧

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

DD51_742

総合スコア14

C

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

ポインタ

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

1グッド

2クリップ

投稿2016/04/29 02:56

編集2016/05/07 03:16

###前提・実現したいこと
現在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次元配列が出てきて、苦労しています。

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

MIURA_Yasuyuki👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MIURA_Yasuyuki

2016/04/29 04:21

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

2016/04/29 13:30

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

2016/04/30 01:05

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

2016/05/06 17: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*) これが複雑になって意図通りになっていないのでは?
guest

回答5

0

ベストアンサー

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

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

C

1#include <stdio.h> 2#include <stdlib.h> 3 4struct head { 5 int size; 6 char ver[10]; 7}; 8 9// callocするときにsizeofの情報が必要なので外で定義 10struct dada_rec { 11 int no; 12 char name[30]; 13}; 14 15struct data { 16 int size; // 0サイズ構造体が作れないので、ダミーでsizeを追加 17 struct dada_rec rec[]; // 可変にできるのは最後だけ、[]は0サイズ 18}; 19 20struct raw { 21 int size; // 0サイズ構造体が作れないので、ダミーでsizeを追加 22 int value[][3][4]; // 可変にできるのは最初の[]だけ、int[3][4]型の可変 23}; 24 25struct data_all { 26 struct head head; 27 struct data *data; // 可変な構造体を途中に書くことはできない 28 struct raw raw; // 最後の場合は可能 29}; 30 31struct data_all *dd; // 可変部分があるため静的に作ることはできない 32 33int main(int argc, char *argv[]) 34{ 35 int rec_size = 100000; 36 37 // dd の領域をヒープで、構造体内の配列を可変長に 38 // 0埋めコード書くのが面倒なのでcalloc 39 dd = (struct data_all *)calloc(1, 40 sizeof(struct data_all) + sizeof(int[3][4]) * rec_size); 41 dd->raw.size = rec_size; 42 43 // dataの領域を作成 44 dd->data = (struct data *)calloc( 45 1, sizeof(struct data) + 46 sizeof(struct dada_rec) * rec_size); 47 dd->data->size = rec_size; 48 49 // ダミーで値を入れてみる 50 for (int i = 0; i < dd->data->size; i++) { 51 dd->data->rec[i].no = i; 52 sprintf(dd->data->rec[i].name, "%08d", i); 53 } 54 for (int i = 0; i < dd->raw.size; i++) { 55 for (int j = 0; j < 3; j++) { 56 for (int k = 0; k < 4; k++) { 57 dd->raw.value[i][j][k] = i * (j + 1) - k; 58 } 59 } 60 } 61 printf("%d: %s\n", dd->data->rec[3].no, dd->data->rec[3].name); 62 printf("%d: %s\n", dd->data->rec[99999].no, dd->data->rec[99999].name); 63 printf("%d\n", dd->raw.value[0][0][0]); 64 printf("%d\n", dd->raw.value[42][1][1]); 65 printf("%d\n", dd->raw.value[99999][2][3]); 66 return 0; 67}

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

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の構造を変えずにするとなると、普通に構造体や配列としてアクセスする方法では無理です。メモリを自分で確保して、直接計算してアクセスするしかないと思います。ということで、書き直しました。

C

1#include <assert.h> 2#include <stdio.h> 3#include <stdlib.h> 4 5struct head { 6 int size; 7 char ver[10]; 8}; 9 10struct data { 11 int no; 12 char name[30]; 13}; 14 15struct data_all { 16 int size; 17 size_t mem_size; 18 void *mem; 19}; 20 21struct data_all data_all_create(int size) 22{ 23 assert(0 <= size); 24 struct data_all dd; 25 dd.size = size; 26 dd.mem_size = sizeof(struct head) + sizeof(struct data) * size + 27 sizeof(int) * 3 * 4 * size; 28 assert(0 <= dd.mem_size); 29 dd.mem = calloc(1, dd.mem_size); 30 return dd; 31} 32void data_all_destroy(struct data_all dd) 33{ 34 free(dd.mem); 35 dd.mem = NULL; 36} 37 38struct head *data_all_head(struct data_all dd) 39{ 40 assert(dd.mem != NULL); 41 return (struct head *)dd.mem; 42} 43struct data *data_all_data(struct data_all dd, int n) 44{ 45 assert(dd.mem != NULL); 46 assert(0 <= n && n < dd.size); 47 return (struct data *)(dd.mem + sizeof(struct head) + 48 sizeof(struct data) * n); 49} 50// data[y][x][n]のこと 51int *data_all_value(struct data_all dd, int n, int x, int y) 52{ 53 assert(dd.mem != NULL); 54 assert(0 <= n && n < dd.size); 55 assert(0 <= x && x < 4); 56 assert(0 <= y && y < 3); 57 size_t forward = sizeof(struct head) + sizeof(struct data) * dd.size + 58 sizeof(int) * (n + dd.size * x + dd.size * 4 * y); 59 assert(forward < dd.mem_size); 60 return (int *)(dd.mem + forward); 61} 62int data_all_get_value(struct data_all dd, int n, int x, int y) 63{ 64 return *data_all_value(dd, n, x, y); 65} 66void data_all_set_value(struct data_all dd, int n, int x, int y, int value) 67{ 68 *data_all_value(dd, n, x, y) = value; 69} 70 71struct data_all dd; // 可変部分があるため静的に作ることはできない 72 73int main(int argc, char *argv[]) 74{ 75 int rec_size = 100000; 76 dd = data_all_create(rec_size); 77 if (dd.mem == NULL) { 78 fprintf(stderr, "Failed to allocate memory.\n"); 79 return 1; 80 } 81 // 既にあるデータ構造を入れる場合は、次のようにする。 82 // memcpy(dd.mem, ○○, dd.mem_size); 83 84 // ダミーで値を入れてみる 85 for (int n = 0; n < dd.size; n++) { 86 struct data *d = data_all_data(dd, n); 87 d->no = n; 88 sprintf(d->name, "%08d", n); 89 } 90 for (int n = 0; n < dd.size; n++) { 91 for (int x = 0; x < 4; x++) { 92 for (int y = 0; y < 3; y++) { 93 // data[y][x][n] = ... 94 data_all_set_value( 95 dd, n, x, y, n * (y + 1) - x); 96 } 97 } 98 } 99 printf("%d: %s\n", data_all_data(dd, 3)->no, 100 data_all_data(dd, 3)->name); 101 printf("%d: %s\n", data_all_data(dd, 99999)->no, 102 data_all_data(dd, 99999)->name); 103 printf("%d\n", data_all_get_value(dd, 0, 0, 0)); // 最小値 104 printf("%d\n", data_all_get_value(dd, 42, 1, 1)); 105 printf("%d\n", data_all_get_value(dd, 99999, 3, 2)); // 最大値 106 data_all_destroy(dd); 107 return 0; 108}

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

投稿2016/04/29 04:54

編集2016/05/06 15:03
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DD51_742

2016/05/06 14: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 自分でも調べて見ますが、何かわかりそうであればアドバイス頂ければ幸いです
raccy

2016/05/06 15: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でよかったのでしょうか?
guest

0

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

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

定義

C

1typedef struct _data { 2 char a; 3 long b; 4 unsigned char c; 5 int sample[1]; 6} 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を確保することとなるので、結果的に、

C

1typedef struct _data { 2 char a; 3 long b; 4 unsigned char c; 5 int sample[NUM_REC]; 6} data ;

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

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

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

投稿2016/04/29 03:12

ItoTomonori

総合スコア1283

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

C

1int rec_size=100000; 2 3struct head { 4 int size; 5 char ver[10]; 6}; 7 8struct data_rec { 9 int no; 10 char name[30]; 11}; 12 13struct data_all { 14 struct head *head; 15 struct data_rec *data; 16 int *value[3][4]; 17}; 18 19struct data_all dd; 20void *mem=malloc(sizeof(head) + sizeof(data_rec) * rec_size + sizeof(int) * 3 * 4 * rec_size); 21 22dd.head = mem; 23dd.data = dd.head + sizeof(head); 24int *p = dd.data + sizeof(data_rec) * rec_size; 25for(int i = 0; i < 3; i++ ){ 26 for(int j = 0; j < 4; j++ ){ 27 dd.value[i][j] = p; 28 p++; 29 } 30}

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

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

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

投稿2016/04/29 08:02

編集2016/04/29 15:08
chun

総合スコア324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

chun

2016/04/29 08:07

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

2016/05/06 14:22

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

0

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

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

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

C

1#include <stdio.h> 2#include <stdlib.h> 3 4struct head 5{ 6 int size; 7 char ver[10]; 8}; 9 10struct data 11{ 12 struct dada_rec 13 { 14 int no; 15 char name[30]; 16 } *rec; // data構造体の中にはこの変数しかないので、これを動的に確保 17}; 18 19struct raw 20{ 21 int *value[3][4]; // 別途確保した領域を指すポインタの二次元配列 22}; 23 24struct data_all 25{ 26 struct head head; 27 struct data data; 28 struct raw raw; 29}; 30 31struct data_all dd; 32 33int main() 34{ 35 int rec_size = 100000; 36 37 // dd.data.recは普通にメモリ確保 38 dd.data.rec = (struct dada_rec *)malloc(rec_size * sizeof(struct dada_rec)); 39 40 // dd.raw.valueは、まず連続した大きな領域を確保して 41 int *raw_data = (int *)malloc(3 * 4 * rec_size * sizeof(int)); 42 // 二次元配列に当該領域のポインタを格納 43 for(int i = 0; i < 3; i++) 44 { 45 for(int j = 0; j < 4; j++) 46 { 47 dd.raw.value[i][j] = &raw_data[(i * 4 + j) * rec_size]; 48 } 49 } 50 51 // データが入れられることを確認 52 53 for(int i = 0; i < rec_size; i++) 54 { 55 dd.data.rec[i].no; 56 sprintf(dd.data.rec[i].name, "i=%d", i); 57 } 58 59 for(int i = 0; i < 3; i++) 60 { 61 for(int j = 0; j < 4; j++) 62 { 63 for(int k = 0; k < rec_size; k++) 64 { 65 dd.raw.value[i][j][k] = i + j + k; 66 } 67 } 68 } 69 70 // いらなくなったら領域解放 71 free(dd.data.rec); 72 free(dd.raw.value[0][0]); 73 74 return 0; 75}

投稿2016/04/29 06:57

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DD51_742

2016/05/06 14:23

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

0

こんにちは。

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

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


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

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

投稿2016/04/29 03:19

編集2016/04/29 03:37
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DD51_742

2016/05/06 14:19

3次元配列の可変長配列について、アドバイスありがとうございます。 ここ10年程C系列の言語はC++しか触っていないので、C99の可変長配列はネット上に散らばる断片的な情報でしか知りませんでした。 個人的には配列は2次元までしか使わないのが、普通かなと思っていましたが既存ソースコードが3次元配列を使用しており、使用するデータも3次元配列に依存する形式なので、困っておりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問