2

これは基本的な質問だと思いますが、これが正当なメモリ割り当て戦略であるかどうかを見つけることができませんでした。ファイルからデータを読み込んでいて、構造体に入力しています。メンバーのサイズは読み取りごとに可変であるため、構造体要素はそのようなポインターです

struct data_channel{
    char *chan_name;
    char *chan_type;
    char *chan_units;
};

したがって、読む前に、各文字列のサイズを把握して、それらにメモリを割り当てることができるようにします。私の質問は、構造体と文字列にすべて 1 つの malloc でメモリを割り当ててから、ポインタを埋めることができるかということです。

chan_name のサイズが 9、chan_type が 10、chan_units が 5 だとします。

struct data_channel *chan;

chan = malloc(sizeof(struct data_channel) + 9 + 10 + 5);
chan->chan_name = chan[1];
chan->chan_type = chan->chan_name + 9;
chan->chan_units = chan->chan_type + 10;

そのため、メモリの配置に関するいくつかの記事を読みましたが、上記のことを行うことが問題であるかどうか、または意図しない結果がどのようなものになるかはわかりません。私はすでにコードに実装しており、うまく機能しているようです。実際には、各構造体には 7 つの要素があり、100 以上のチャネルを持つことができるため、これらすべてのポインターを追跡する必要はありません。もちろん、これは 700 個のポインターと各構造体のポインターを合わせた合計 800 個のポインターを意味します。また、それらすべてを解放する方法を考案する必要があります。また、この戦略を文字列の配列に適用したいと思います。その配列には、ポインターの配列が必要です。現在、データ型を混在させる構造はありませんが、それが問題になる可能性がありますが、問題になる可能性がありますか?

4

4 に答える 4

3

これは、要素の種類によって部分的に異なります。確かに文字列でそれを行うことができます。他のいくつかのタイプでは、配置とパディングの問題について心配する必要があります。

struct data_channel
{
    char *chan_name;
    char *chan_type;
    char *chan_units;
};

struct data_channel *chan;
size_t name_size = 9;
size_t type_size = 10;
size_t unit_size = 5;

chan = malloc(sizeof(struct data_channel) + name_size + type_size + unit_size);
if (chan != 0)
{
    chan->chan_name  = (char *)chan + sizeof(*chan);
    chan->chan_type  = chan->chan_name + name_size;
    chan->chan_units = chan->chan_type + type_size;
}

これは実際には問題なく動作します — 標準が標準化される前から長い間行われていました。標準がこれを禁止する理由がすぐにはわかりません。

intさらに厄介なのは、2 つの文字列だけでなく、たとえばの配列を割り当てる必要がある場合です。次に、アライメントの問題について心配する必要があります。

struct data_info
{
    char *info_name;
    int  *info_freq;
    char *info_unit;
};

size_t name_size = 9;
size_t freq_size = 10;
size_t unit_size = 5;
size_t nbytes = sizeof(struct data_info) + name_size + freq_size * sizeof(int) + unit_size;
struct data_info *info = malloc(nbytes);

if (info != 0)
{
    info->info_freq = (int *)((char *)info + sizeof(*info));
    info->info_name = (char *)info->info_freq + freq_size * sizeof(int);
    info->info_unit = info->info_name + name_size;
}

intこれは、最も厳密に整列された型 ( の配列) を最初に割り当て、次に文字列を後で割り当てるという単純な方法を採用しています。ただし、この部分では、移植性について判断を下す必要があります。コードは実際に移植可能であると確信しています。

C11には、この回答を変更できるアライメント機能(_Alignofand _Alignasand <stdalign.h>、plus max_align_tin <stddef.h>)があります(ただし、十分に調査していないため、方法はまだわかりません)が、ここで概説されている手法は、Cのどのバージョンでも機能します。データの配置には注意してください。

構造体に単一の配列がある場合、C99 はフレキシブル配列メンバー(FAM)と呼ばれる古い「構造体ハック」の代替手段を提供することに注意してください。これにより、配列を構造体の最後の要素として明示的に持つことができます。

    struct data_info
    {
        char *info_name;
        char *info_units;
        int  info_freq[];
    };

    size_t name_size = 9;
    size_t freq_size = 10;
    size_t unit_size = 5;
    size_t nbytes = sizeof(struct data_info) + name_size + freq_size * sizeof(int) + unit_size;
    struct data_info *info = malloc(nbytes);

    if (info != 0)
    {
        info->info_name  = ((char *)info + sizeof(*info) + freq_size * sizeof(int));
        info->info_units = info->info_name + name_size;
    }

info_freqこの例では、FAM を初期化する手順がないことに注意してください。このように複数の配列を持つことはできません。

概要を説明した手法は、構造体の配列 (少なくとも外部構造体の配列) には簡単に適用できないことに注意してください。かなりの努力をすれば、それを機能させることができます。また、注意してくださいrealloc()。スペースを再割り当てする場合、データが移動した場合はポインターを修正する必要があります。

もう 1 つのポイント: 特に 64 ビット マシンでは、文字列のサイズが十分に均一である場合は、ポインターを使用する代わりに、構造体に配列を割り当てる方がよいでしょう。

struct data_channel
{
    char chan_name[16];
    char chan_type[16];
    char chan_units[8];
};

これは 40 バイトを占有します。64 ビット マシンでは、元のデータ構造は 3 つのポインター用に 24 バイトを占有し、(9 + 10 + 5) バイトのデータ用に別の 24 バイトを占有し、合計 48 バイトが割り当てられます。

于 2013-08-27T13:40:17.580 に答える
0

ヨアヒムとジョナサンの答えは素晴らしいです。私が言及したい唯一の追加はこれです。

別個の malloc と free により、バッファー オーバーラン、解放後のアクセスなどの基本的な保護が得られます。Valgrind のような機能ではなく、基本的なことを意味します。1 つのチャンクを割り当てて内部的に分配すると、この機能が失われます。

将来、malloc が完全に異なるサイズのものである場合、別の malloc は、特に異なる時間にそれらを解放する場合に、malloc 実装内の異なる割り当てバケットから来る効率を購入する可能性があります。

最後に考慮しなければならないことは、malloc を呼び出す頻度です。頻繁に行われる場合、複数の malloc のコストが高くつく可能性があります。

于 2013-08-27T15:41:57.987 に答える