C 分配太少的空间是否安全(如果您知道您不需要它)?
因此,C99为常用的“灵活数组成员”黑客提供了便利,允许我们制作C 分配太少的空间是否安全(如果您知道您不需要它)?,c,malloc,flexible-array-member,C,Malloc,Flexible Array Member,因此,C99为常用的“灵活数组成员”黑客提供了便利,允许我们制作structs,以满足我们的大小要求。我怀疑在大多数理智的实现中这样做是完全安全的,但是如果我们知道在某些情况下不需要结构的某些成员,那么在C中“分配不足”是否合法 抽象例子 假设我有一个类型: struct a { bool data_is_x; void * data; size_t pos; }; 如果data\u是x,则data的类型是需要使用pos成员的类型。否则,使用此struct的函数将不需要此str
struct
s,以满足我们的大小要求。我怀疑在大多数理智的实现中这样做是完全安全的,但是如果我们知道在某些情况下不需要结构的某些成员,那么在C中“分配不足”是否合法
抽象例子
假设我有一个类型:
struct a {
bool data_is_x;
void * data;
size_t pos;
};
如果data\u是x
,则data
的类型是需要使用pos
成员的类型。否则,使用此struct
的函数将不需要此struct
的特定副本的pos
成员。本质上,struct
携带关于它是否有pos
成员的信息,并且该信息在struct
的生命周期内不会改变(除了邪恶的恶作剧,它几乎可以破坏任何东西)。可以说:
struct a *a = malloc(data_is_x ? sizeof(struct a) : offsetof(struct a, pos));
只有在需要时,哪个会为pos
成员分配空间?或者使用对struct
指针来说太小的强制转换空间是否违反了约束,即使您从未使用过相关的成员
具体例子
我的实际用例有点复杂;这主要是为了让你明白我为什么要这么做:
typedef struct {
size_t size;
void * data;
size_t pos;
} mylist;
mylist *list = malloc(size ? sizeof(mylist) : offsetof(mylist, pos));
mylist\u create
的代码指定,对于size>0
,data
是一个连续数据数组,其长度为size
项(无论项是什么),但对于size==0
则是包含项的双链接列表的当前节点。使用mylist
s的所有函数都将检查size==0
。如果是这样,他们将把数据作为一个链表处理,“当前”索引是data
指向的节点。如果没有,他们将以数组的形式处理数据,并将“当前”索引存储在pos
中
现在,如果size==0
我们并不真正需要pos
成员,但是如果size>0
我们会。所以我的问题是,这样做合法吗:
typedef struct {
size_t size;
void * data;
size_t pos;
} mylist;
mylist *list = malloc(size ? sizeof(mylist) : offsetof(mylist, pos));
如果我们保证(对未定义行为的惩罚)当
size==0
时,我们不会尝试(或需要)访问pos
成员?或者它是否在标准的某个地方说,它甚至可以考虑这样做?malloc
本身根本不关心为一个结构分配多少内存,未定义的是块外内存的解引用。从C996.5.3.2地址和间接运算符
:
如果为指针指定了无效值,则一元*运算符的行为未定义
从7.20.3内存管理功能中,我们发现(我的斜体):
如果分配成功,则返回的指针将适当对齐,以便可以将其分配给指向任何类型对象的指针,然后用于访问所分配空间中的此类对象或此类对象的数组(直到空间被显式释放)
因此,您可以执行以下操作:
typedef struct { char ch[100]; } ch100;
ch100 *c = malloc (1);
而且,如果您只尝试使用c->ch[0]
做任何事情,这是完全可以接受的
对于您的具体示例,假设您关心的是存储空间,我不太确定我是否会那么担心。如果您出于其他原因而担心,请忽略这一点,特别是因为标准没有强制要求包含其中的假设
根据我的理解,你有一个结构:
typedef struct {
size_t size;
void * data;
size_t pos;
} mylist;
如果只想使用数据
,其中大小
为0,并且数据
和pos
都大于0。这就排除了将数据
和pos
放在一个联合体中的使用
大量的malloc
实现会将您请求的空间四舍五入到16字节的倍数(或更大的2次方),以缓解内存碎片问题。这当然不是标准所要求的,但它是相当常见的
假设(例如)32位指针和size\t
,12个字节的结构很可能占用16字节的arena头和16字节的数据块。即使您只要求8个字节(即没有pos
),该块仍然是16字节
如果您有64位指针和size\t
类型,则可能会有所不同-有pos
的为24字节,没有的为16字节
但即便如此,除非您分配了大量这些结构,否则这可能不是问题。这是完全合法的,但您可能应该通过使用两个结构来减少其模糊性,当您阅读时:
struct leaf_node {
size_t size;
void *data;
size_t pos;
};
struct linked_node {
size_t size;
void *next;
};
void *in = ...;
if (*(size_t*)(in) == 0) {
struct leaf_node *node = in;
...
} else {
struct linked_node *node = in;
....
}
这与paxdiablo引用的标准更加密切相关,即可以将指针强制转换为任何数据指针。如果这样做,您还将始终确保将其转换为适合分配的缓冲区的结构(这是一项不必要但方便的壮举)
paxdiablo所说的在32位系统上最小大小为16字节的说法通常是正确的,但您仍然可以分配大块来解决这个问题
在32位系统上,链接的_节点将使用8个字节。您必须使用池来从您试图做的事情中获益
struct leaf_node *leaf_pool = malloc(N*sizeof(struct leaf_node));
struct linked_node *linked_pool = malloc(N*sizeof(struct linked_node));
当然,您永远不应该重新定位池,而是根据需要分配新池并重用节点。在这种情况下,单个leaf\u节点将使用12个字节
同样的情况也适用于链接的_节点
,如果在池中分配它们,将使用8个字节而不是16个字节
只要您的结构没有在GCC中使用\uuuuu属性((打包))
,就不会出现性能瓶颈,在这种情况下,您的结构可能会非常不协调。特别是如果您的结构中有一个额外的字符,使其大小为13字节
现在如果w