【问题标题】:allocate struct and memory for elements in one malloc为一个 malloc 中的元素分配结构和内存
【发布时间】:2013-08-30 06:02:38
【问题描述】:

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

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

【问题讨论】:

  • 有一个明显的错误,会导致代码无法编译。
  • @JoachimPileborg - 你的意思是 chan 不是一个数组?
  • 这看起来非常危险且不可携带。我只需构建一个分配例程(用于结构)和一个释放例程(用于结构)并完成它。无论如何你都必须分配和释放它,所以只需封装你不想处理的额外指针。
  • @MartinJames 不,OP 应该使用 &char[1] 代替。并将其类型转换为正确的类型。
  • 我在指针中看不到太多意义?为什么不能直接将 char 数组推入 data_channel?

标签: c memory-management malloc


【解决方案1】:

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

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

【讨论】:

  • +1。如果您有除char 以外的其他类型,则可能存在内存对齐问题,这就是大多数人会避免使用此方法的原因。
【解决方案2】:

这部分取决于元素类型。您当然可以使用字符串来做到这一点;对于其他一些类型,您必须担心对齐和填充问题。

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>,加上 max_align_t 中的 <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;
    }

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

请注意,概述的技术不能轻易应用于结构数组(至少是外部结构的数组)。如果你付出相当大的努力,你可以让它发挥作用。另外,请注意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 个字节。

【讨论】:

    【解决方案3】:

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

    #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;
    }
    

    【讨论】:

    • 这是使用 C99 之前的“struct hack”。它在实践中仍然有效。
    【解决方案4】:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2015-01-05
      • 1970-01-01
      • 1970-01-01
      • 2015-11-09
      • 2021-05-29
      • 2015-06-27
      • 1970-01-01
      • 2016-09-22
      • 1970-01-01
      相关资源
      最近更新 更多