web-dev-qa-db-ja.com

Cの構造体の中に動的配列を含める方法は?

私は見回しましたが、よくある質問と思われるものに対する解決策を見つけることができませんでした。ここに私が持っているコードがあります:

 #include <stdlib.h>

struct my_struct {
    int n;
    char s[]
};

int main()
{
    struct my_struct ms;
    ms.s = malloc(sizeof(char*)*50);
}

そして、ここにgccが私に与えるエラーがあります:エラー:柔軟な配列メンバーの無効な使用

構造体内でsの宣言を宣言すると、コンパイルすることができます

char* s

これはおそらく優れた実装です(ポインタ演算は配列よりも高速ですか?)が、cでは

char s[]

と同じです

char* s
42
Tom

C99が「柔軟な配列メンバー」としてそれを祝福するまで、「構造ハック」と呼ばれていました。 (おそらくとにかく)エラーが発生する理由は、セミコロンが後に続く必要があるためです:

#include <stdlib.h>

struct my_struct {
    int n;
    char s[];
};

これにスペースを割り当てるときは、構造体のサイズを割り当てますplus配列に必要なスペースの量:

struct my_struct *s = malloc(sizeof(struct my_struct) + 50);

この場合、柔軟な配列メンバーはcharの配列であり、sizeof(char)== 1であるため、そのサイズで乗算する必要はありませんが、他のmallocと同様に他のタイプの配列:

struct dyn_array { 
    int size;
    int data[];
};

struct dyn_array* my_array = malloc(sizeof(struct dyn_array) + 100 * sizeof(int));

編集:これは、メンバーをポインターに変更した場合とは異なる結果になります。その場合、(通常)2つの個別の割り当てが必要です。1つは構造体自体用で、もう1つはポインターが指す「余分な」データ用です。柔軟な配列メンバーを使用すると、すべてのデータを単一のブロックに割り当てることができます。

67
Jerry Coffin

最初に何をしようとしているかを決める必要があります。


内部に[独立]配列へのポインターを持つ構造体が必要な場合は、次のように宣言する必要があります。

struct my_struct { 
  int n; 
  char *s;
}; 

この場合、実際の構造体オブジェクトを任意の方法で作成できます(たとえば、自動変数など)

struct my_struct ms;

そして、独立して配列のメモリを割り当てます

ms.s = malloc(50 * sizeof *ms.s);  

実際、配列メモリを動的に割り当てる一般的な必要性はありません。

struct my_struct ms;
char s[50];

ms.s = s;

これらはすべて、これらのオブジェクトに必要なライフタイムの種類によって異なります。構造体が自動の場合、ほとんどの場合、配列も自動になります。 structオブジェクトowns配列メモリの場合、他の方法で行う意味はありません。構造体自体が動的である場合、配列も通常動的でなければなりません。

この場合、構造体と配列の2つの独立したメモリブロックがあることに注意してください。


まったく異なるアプローチは、「構造ハック」イディオムを使用することです。この場合、配列は構造体の不可欠な部分になります。両方ともメモリの単一ブロックに存在します。 C99では、構造体は次のように宣言されます。

struct my_struct { 
  int n; 
  char s[];
}; 

オブジェクトを作成するには、全体を動的に割り当てる必要があります

struct my_struct *ms = malloc(sizeof *ms + 50 * sizeof *ms->s);

この場合のメモリブロックのサイズは、構造体のメンバーと実行時サイズの末尾配列に対応するように計算されます。

この場合、静的または自動オブジェクトなどの構造体オブジェクトを作成するオプションがないことに注意してください。最後に柔軟な配列メンバーを持つ構造体は、Cでのみ動的に割り当てることができます。


配列よりもポインタの高速化についてのあなたの仮定は、絶対に間違っています。配列は、定義によりポインター演算を介して機能するため、基本的に同じです。さらに、(ポインターに減衰しない)純粋な配列は、一般的にポインターオブジェクトよりも少し高速です。ポインタ値はメモリから読み取る必要がありますが、メモリ内の配列の位置は配列オブジェクト自体から「既知」(または「計算」)です。

19
AnT

サイズの指定されていない配列の使用は、構造体の最後でのみ許可されており、一部のコンパイラでのみ機能します。これは非標準のコンパイラ拡張です。 (C++ 0xがこれを許可することを覚えていると思いますが。)

ただし、配列は構造体とは別に割り当てられません。したがって、配列部分だけでなく、my_structをすべて割り当てる必要があります。

私がしていることは、配列に小さいがゼロ以外のサイズを単に与えることです。通常、文字配列の場合は4、wchar_t配列の場合は2で、32ビットのアライメントを保持します。

次に、割り当てを行うときに、宣言された配列のサイズを考慮することができます。私はしばしば、スロップがヒープマネージャーがどのような場合でも動作する粒度よりも小さいという理論に基づいていません。

また、割り当てにsizeof(char *)を使用しないでください。

これは私がすることです。

struct my_struct {
    int nAllocated;
    char s[4]; // waste 32 bits to guarantee alignment and room for a null-terminator
};

int main()
{
    struct my_struct * pms;
    int cb = sizeof(*pms) + sizeof(pms->s[0])*50;
    pms = (struct my_struct*) malloc(cb);
    pms->nAllocated = (cb - sizoef(*pms) + sizeof(pms->s)) / sizeof(pms->s[0]);
}
1
John Knoeller

コンパイラーは、s []に自動変数を宣言することを選択した場合に、s []に割り当てる必要のあるスペースの量を知らないと思います。

私はベンが言ったことに同意し、あなたの構造体を宣言します

_struct my_struct {
    int n;
    char s[1];
};
_

また、ストレージに関するコメントを明確にするために、_char *s_を宣言すると(動的に割り当てられるため)構造体をスタックに配置せず、ヒープにsを割り当てます。配列の最初のsizeof(char *)バイトをポインタとして使用しているため、自分が考えているデータを操作することはできず、おそらく致命的です。

ポインタと配列の操作は同じ方法で実装できますが、同じものではないことを覚えておくことが重要です。

0
Duncan

配列はポインターに解決され、ここではmustschar *sとして定義します。構造体は基本的にコンテナであり、(IIRC)固定サイズである必要があります。そのため、内部に動的なサイズの配列を持つことは不可能です。とにかくメモリをmallocingしているので、これはあなたが望んでいることに何の違いももたらさないはずです。

基本的に、sはメモリの場所を示しています。 s[0]のような表記を使用して、後でこれにアクセスできることに注意してください。

0
Mark Elliot

ポインター演算は配列より高速です、はい?

まったく違います-実際は同じです。配列はコンパイル時にポインター演算に変換されます。

char test[100];
test[40] = 12;

// translates to: (test now indicates the starting address of the array)
*(test+40) = 12;
0