我确信这是一个基本问题,但我一直无法确定这是否是一个合法的内存分配策略。我正在从一个文件中读取数据,并填充一个结构。成员的大小在每次读取时都是可变的,所以我的结构元素是指针,就像一样
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;
}
单独的malloc和frees为您购买了一些基本的保护,如缓冲区溢出、免费等等。我指的是基本的,而不是像Valgrind那样的功能。分配一个单独的区块并在内部分发会导致该功能的丢失。
将来,如果malloc完全用于不同的大小,那么单独的malloc可能会为您从malloc实现中的不同分配桶中获得效率,尤其是如果您要在不同的时间释放它们。
最后需要考虑的是调用mallocs的频率。如果它是频繁的,那么多个malloc的成本可能是昂贵的。