c-为一个malloc中的元素分配结构和内存



我确信这是一个基本问题,但我一直无法确定这是否是一个合法的内存分配策略。我正在从一个文件中读取数据,并填充一个结构。成员的大小在每次读取时都是可变的,所以我的结构元素是指针,就像一样

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

所以在阅读之前,我想知道每个字符串的大小,这样我就可以为它们分配内存了。我的问题是,我可以在一个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个。我还必须想出一个办法来解放他们。我还想把这个策略应用于字符串数组,然后我需要有一个指针数组。我现在没有任何可以混合数据类型的结构。这可能是个问题,但我可能会是个问题吗?

如果chan_name是8个字符的字符串,chan_type是9个字符的串,chan_units是4个字符的列,那么是的,当您修复分配给chan_name时的编译错误时,它会正常工作。

如果为结构加上所有字符串(包括它们的字符串终止符)分配了足够的内存,那么可以使用这样的方法。也许不是所有人都推荐,但它会起作用的。

它在一定程度上取决于元素类型。你肯定可以用字符串来做;对于其他一些类型,您必须担心对齐和填充问题。

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数组以及两个字符串。然后你必须担心对齐问题。

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具有对齐功能(_Alignof_Alignas<stdalign.h>,加上<stddef.h>中的max_align_t),可以改变这个答案(但我还没有对它们进行充分的研究,所以我还不确定如何进行),但这里概述的技术可以在任何版本的C中使用,前提是要小心数据的对齐。

请注意,如果结构中只有一个数组,那么C99提供了一种替代旧的"structhack"的方法,称为灵活数组成员(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;
    }

请注意,在本例中没有初始化FAM info_freq的步骤。不能有多个这样的数组。

请注意,概述的技术不能容易地应用于结构阵列(至少外部结构阵列)。如果你付出相当大的努力,你就能成功。另外,注意realloc();如果重新分配空间,则必须在数据移动时修复指针。

还有一点:尤其是在64位机器上,如果字符串的大小足够均匀,那么可能会更好地在结构中分配数组,而不是使用指针。

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

这占用了40个字节。在64位机器上,原始数据结构将占用用于三个指针的24个字节和用于数据的(9+10+5)字节的另外24个字节,总共分配48个字节。

我知道当你在一个结构的末尾有一个数组时,有一种确定的方法可以做到这一点,但由于你所有的数组都有相同的类型,你可能很幸运。确定的方法是:

#include <stddef.h>
#include <stdlib.h>
struct StWithArray
{
    int blahblah;
    float arr[1];
};
struct StWithArray * AllocWithArray(size_t nb)
{
    size_t size = nb*sizeof(float) + offsetof(structStWithArray, arr);
    return malloc(size);
}

在结构中使用实际阵列可以确保对齐得到尊重。

现在将其应用于您的案例:

#include <stddef.h>
#include <stdlib.h>
struct data_channel
{
    char *chan_name;
    char *chan_type;
    char *chan_units;
    char actualCharArray[1];
};
struct data_channel * AllocDataChannel(size_t nb)
{
    size_t size = nb*sizeof(char) + offsetof(data_channel, actualCharArray);
    return malloc(size);
}
struct data_channel * CreateDataChannel(size_t length1, size_t length2, size_t length3)
{
    struct data_channel * pt = AllocDataChannel(length1 + length2 + length3);
    if(pt != NULL)
    {
        pt->chan_name = &pt->actualCharArray[0];
        pt->chan_type = &pt->actualCharArray[length1];
        pt->chan_name = &pt->actualCharArray[length1+length2];
    }
    return pt;
}
Joachim和Jonathan的回答很好。我唯一想提到的是这一点。

单独的malloc和frees为您购买了一些基本的保护,如缓冲区溢出、免费等等。我指的是基本的,而不是像Valgrind那样的功能。分配一个单独的区块并在内部分发会导致该功能的丢失。

将来,如果malloc完全用于不同的大小,那么单独的malloc可能会为您从malloc实现中的不同分配桶中获得效率,尤其是如果您要在不同的时间释放它们。

最后需要考虑的是调用mallocs的频率。如果它是频繁的,那么多个malloc的成本可能是昂贵的。

相关内容

  • 没有找到相关文章

最新更新