安全的为列表节点分配内存的方法

3
在简单列表中,例如:
struct Node {  
    Node *next;  
    void *data;  
}  

如果我在单个分配中分配Node和Data (前提是我知道大小),是否会有任何问题,例如:

Node * t = (Node*)malloc(sizeof(Node) + DataSize));  

始终将数据分配到已分配块的末尾。

t->data = (BYTE*)t+ sizeof(Node); /* BYTE is byte length, can use char in gcc */

节点和数据将在一次操作中被删除,因此将它们紧密耦合在一起(通过设计)没有真正的问题。

我正在关注可移植性问题(特别是打包方面)或其他未知问题?

这种分配方式是否安全和可移植?

4个回答

4

正如dirkgently所说,在C语言中不要对malloc()的返回值进行类型转换,这样做是无用的,并且可能会隐藏错误。

此外,为了计算Node头之后的地址,我发现像这样做更加简洁:

t->data = t + 1;

这个方法可行是因为t是一个有类型的指针,所以在它上面进行算术运算是没问题的。加1会增加所指向数据的大小,即在这种情况下是sizeof (Node)。我发现在这特定的情况下这种用法很惯用,用于计算刚刚被malloc()分配之后的地址(当“something”是一个定义良好、大小静态已知的类型时,如此例中的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;
}

就是这样。注意在malloc()调用中对指针目标使用sizeof,以避免重复类型名称并使易于忘记的依赖关系(如果类型发生更改)。


好吧,他确实说过可移植性..如果一个C++编译器要处理它,malloc的返回值必须被强制转换。 - Tim Post
2
好的,我永远不会认为代码“可移植”是一个例子。编写C代码以编译为C ++不是编写可移植的C代码,这是完全不同的事情。对我来说,“可移植”意味着“可以使用其他C编译器进行编译”,不依赖于架构细节等。 - unwind
@unwind:您觉得哪一部分不可取? - dirkgently
@dirkgently:在typedef完成之前,您仍然使用了Node类型。您的代码无法编译,至少在gcc中是如此。 - unwind
@unwind:已更新。但在Comeau编译器上出现了问题,它在C89/90和C99模式下都接受它。是时候深入研究C99规范了。 - dirkgently

3

从技术上讲,如果数据具有对齐要求,则可能不安全。malloc()返回适合所有类型的指针,很可能t+1也是对齐的。但只是很可能,不能保证。


2

在创建别名之前,您无法将Node用作类型。请使用以下方法:

typedef struct Node {
   struct Node* n;
   void* data;
}Node;

如果您使用C编译器,那么没有必要将malloc的结果强制转换。我发现以下代码更易于维护和阅读:
Node* t = malloc(sizeof *t + DataSize);

BYTE不是语言定义的标准类型,因此不可移植。以下代码行试图实现什么目的?

这段话提到了BYTE不是语言定义的标准类型,因此不具备跨平台移植性。该语句的意图需要进一步解释。
t->data = (BYTE*)t+ sizeof(Node); 

如果您想分配某些内容,可以使用以下方法:
t->data = pointer to some data ...

如果您想获取字节偏移量,请使用offsetof宏。

打包是实现特定的。您必须参考适当的编译器文档并查看可用的内容。

此外,您可能希望有一个head对象,以维护列表的一些管理信息(长度等)。


我猜你需要为指向非void*类型的分配进行转换。BYTE/char用于正确的指针加法运算。 - FL4SOF

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)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接