2

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

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 个。我还必须设计一种方法来释放它们。我还想将此策略应用于字符串数组,然后我需要一个指针数组。我现在没有任何可以混合数据类型的结构,这可能是个问题,但我可能会是个问题吗?

4

4 回答 4

5

如果chan_name是 8 个字符的字符串,chan_type是 9 个字符的字符串,并且chan_units是 4 个字符的字符串,那么是的,当您修复分配给chan_name.

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

于 2013-08-27T13:17:56.693 回答
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,比如说,以及两个字符串。然后你必须担心对齐问题。

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_Alignas<stdalign.h>,加上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(); 如果重新分配空间,如果数据已移动,则必须修复指针。

另一点:特别是在 64 位机器上,如果字符串的大小足够统一,您可能会更好地在结构中分配数组,而不是使用指针。

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

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

于 2013-08-27T13:40:17.580 回答
1

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

#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;
}
于 2013-08-27T13:25:45.303 回答
0

Joachim 和 Jonathan 的回答很好。我想提的唯一补充就是这个。

单独的 malloc 和 free 可以为您提供一些基本保护,例如缓冲区溢出、释放后访问等。我的意思是基本功能,而不是 Valgrind 之类的功能。分配一个单独的块并在内部分配它会导致此功能的丢失。

将来,如果 malloc 的大小完全不同,那么单独的 malloc 可能会为您带来来自 malloc 实现内部不同分配桶的效率,特别是如果您要在不同时间释放它们。

您必须考虑的最后一件事是调用 malloc 的频率。如果它很频繁,那么多个 malloc 的成本可能会很高。

于 2013-08-27T15:41:57.987 回答