memcpy 到动态存储结构是否安全?

Is it safe to memcpy to a dynamic storage struct?

上下文:

我正在审查一些代码,这些代码将数据从 IO 描述符接收到字符缓冲区,对其进行一些控制,然后使用接收到的缓冲区的一部分来填充结构,突然想知道是否违反了严格的别名规则参与其中。

这里是简化版

#define BFSZ 1024
struct Elt {
   int id;
   ...
};

unsigned char buffer[BFSZ];
int sz = read(fd, buffer, sizeof(buffer)); // correctness control omitted for brievety

// search the beginning of struct data in the buffer, and process crc control
unsigned char *addr = locate_and_valid(buffer, sz);

struct Elt elt;

memcpy(&elt, addr, sizeof(elt)); // populates the struct

// and use it
int id = elt.id;
...

到目前为止,还不错。提供缓冲区确实包含结构的有效表示 - 假设它是在同一平台上生成的,因此没有字节顺序或填充问题 - memcpy 调用已填充结构并且可以安全地使用它。

问题:

如果结构是动态分配的,则它没有声明的类型。让我们将最后一行替换为:

struct Elt *elt = malloc(sizeof(struct Element)); // no declared type here

memcpy(elt, addr, sizeof(*elt)); // populates the newly allocated memory and copies the effective type

// and use it
int id = elt->id;  // strict aliasing rule violation?
...

C 语言草案 n1570 在 6.5 表达式 §6 中说

The effective type of an object for an access to its stored value is the declared type of the object, if any.87) If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.

buffer 确实有一个有效类型,甚至还有一个声明类型:它是一个 unsigned char 的数组。这就是代码使用 memcpy 而不是像这样的别名的原因:

struct Elt *elt = (struct Elt *) addr;

这确实违反了严格的别名规则(并且可能还会出现对齐问题)。但是如果 memcpyelt 指向的区域一个有效类型的 unsigned char 数组,一切都会丢失。

问题:

memcpy从字符类型数组到没有声明类型的对象是否给出有效的字符数组类型?

免责声明:

我知道它可以在所有常见编译器上运行而不会发出警告。我只想知道我对标准的理解是否正确


为了更好地展示我的问题,让我们考虑具有sizeof(struct Elt2)<= sizeof(struct Elt)

的不同结构Elt2
struct Elt2 actual_elt2 = {...};

对于静态或自动存储,我不能重用对象内存:

struct Elt elt;
struct Elt2 *elt2 = &elt;
memcpy(elt2, &actual_elt2, sizeof(*elt2));
elt2->member = ...           // strict aliasing violation!

虽然动态的没问题(关于它的问题):

struct Elt *elt = malloc(sizeof(*elt));
// use elt
...
struct Elt2 *elt2 = elt;
memcpy(elt2, &actual_elt2, sizeof(*elt2));
// ok, memory now have struct Elt2 effective type, and using elt would violate strict aliasing rule
elt2->member = ...;        // fine
elt->id = ...;             // strict aliasing rule violation!

从 char 数组复制有什么不同?

代码很好,没有严格的别名违规。指向的数据具有有效类型,因此粗体引用文本不适用。这里适用的是你遗漏的部分,6.5/6的最后一句:

For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

所以指向对象的有效类型变为struct Elt。 malloc 返回的指针确实指向一个没有delcared 类型的对象,但是一旦指向它,有效类型就变成了struct 指针的有效类型。否则C程序根本无法使用malloc。

使代码安全的原因还在于您正在复制 数据到该结构中。如果您只是分配一个 struct Elt* 指向与 addr 相同的内存位置,那么您将遇到严格的别名冲突和 UB。

伦丁的回答是正确的;你所做的很好(只要数据对齐且字节顺序相同)。

我想指出,这与其说是 C 语言规范的结果,不如说是 硬件 工作方式的结果。因此,没有一个权威的答案。 C 语言规范定义了 语言 的工作方式,而不是该语言在不同系统上的编译或实现方式。

这是一篇关于 SPARC 与 Intel 处理器上的内存对齐和严格别名的有趣文章(请注意,完全相同的 C 代码执行不同,并且在一个平台上工作时会在另一个平台上出错): https://askldjd.com/2009/12/07/memory-alignment-problems/

从根本上说,两个相同的结构,在具有相同字节序和内存对齐的同一系统上,必须通过 memcpy 工作。如果没有,那么计算机将无能为力。

最后,以下问题解释了更多关于系统内存对齐的问题,joshperry 的回答应该有助于解释为什么这是一个 硬件 问题,而不是 语言 问题: Purpose of memory alignment