在一个简单的清单中,例如:
struct Node {
Node *next;
void *data;
}
如果我在单个分配中分配节点和数据(如果我知道大小),有什么问题,比如
Node * t = (Node*)malloc(sizeof(Node) + DataSize));
并始终在分配的块的末尾分配数据,
t->data = (BYTE*)t+ sizeof(Node); /* BYTE is byte length, can use char in gcc */
节点和数据将在sinlge go中被删除,因此没有真正的问题紧密耦合它们(按设计)
我正在研究可移植性问题(特别是包装)或其他未知问题?
这种分配方式安全且便携吗?
答案 0 :(得分:4)
正如Dirkgently所说,不要假设C中malloc()
的返回,这样做是没用的,可以隐藏错误。
另外,为了计算Node标头后面的地址,我发现这样做更清晰:
t->data = t + 1;
这是有效的,因为t
是一个类型化的指针,所以算术运算正常。添加一个以指向数据的大小递增它,即在这种情况下为sizeof (Node)
。我发现这个用法惯用于这种特殊情况,即紧接着malloc()
ed之后的地址计算地址(当“某些东西”是具有静态已知大小的明确定义的类型时,作为Node
结构在这种情况下,当然)。
这有以下好处:
sizeof
,所以更短。我意识到在正确声明之前使用Node
类型时出现错误。我不同意dirkgently的解决方案,这是它的外观,在C:
/* This introduces the type name "Node", as an alias for an undefined struct. */
typedef struct Node Node;
struct Node {
Node *next; /* This is OK, the compiler only needs to know size of pointer. */
void *data;
};
为了完整性,因为我从不厌倦地展示我应该如何编写这样的代码,这里是一个创建一个新节点来保存n个字节数据的函数示例:
Node * node_new(size_t n)
{
Node *node;
if((node = malloc(sizeof *node + n)) != NULL)
{
node->next = NULL;
node->data = node + 1;
}
return node;
}
就是这样。请注意在sizeof
调用中对指针目标使用malloc()
,以避免重复类型名称并在类型应该更改时产生易于忘记的依赖关系。
答案 1 :(得分:3)
从技术上讲,如果数据具有对齐要求,则可能不安全。 malloc()返回一个适合所有类型的指针,t+1
也可能是对齐的。可能,但不能保证。
答案 2 :(得分:2)
在创建别名之前,不能将Node
用作类型。使用:
typedef struct Node {
struct Node* n;
void* data;
}Node;
如果您要坚持使用C编译器,则无需转换malloc
的结果。我发现以下内容更易于维护和阅读:
Node* t = malloc(sizeof *t + DataSize);
BYTE
不是语言定义的标准类型,因此不可移植。以下几行试图完成什么?
t->data = (BYTE*)t+ sizeof(Node);
如果要分配某些内容,请填写以下内容:
t->data = pointer to some data ...
如果要获取字节偏移量,请使用offsetof
宏。
包装是特定于实施的。您必须参考相应的编译器文档并查看可用的内容。
此外,您可能希望拥有一个head
对象来维护有关列表的管理信息(长度等)。
答案 3 :(得分:1)
我同意@MSalters。只要你有一个间接级别(data
指针),你也可以做正确的事情并为它分配一个单独的块。
现在,另一种方法是使用C99中定义的灵活数组成员:
typedef struct Node {
struct Node *next;
char data[];
} Node;
Node *n = malloc(sizeof(*n) + DataSize * sizeof(*data));
//if (n != NULL), access data[0 ~ DataSize-1]
sizeof(struct Node)
补充了适当数量的填充(如果有),以满足data
的对齐要求,并且data[]
的行为就像它被声明为数组一样适合malloc()
返回的内存块的最大可能大小。这适用于所有类型的data
,而不仅仅是char
(因此第二个sizeof
)。