C语言 对动态存储结构进行内存是否安全?



上下文:

我正在查看一些代码,这些代码将数据从 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中说

用于访问其存储值的对象的有效类型是声明的类型 对象,如果有的话.87) 如果值通过 左值的类型不是字符类型,则左值的类型变为 该访问和不修改的后续访问的对象的有效类型 存储的值。如果将值复制到没有声明类型的对象中使用 memcpy 或 memmove,或被复制为字符类型的数组,然后是有效类型 该访问的修改对象以及不修改 值是从中复制值的对象的有效类型(如果有)。

buffer确实有一个有效的类型,甚至是一个声明的类型:它是一个unsigned char数组。这就是为什么代码使用memcpy而不是单纯的别名的原因,例如:

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

这确实是严格的混叠规则违规(并且可能还伴随着对齐问题)。但是,如果memcpyelt所指的区域给出了有效类型的无符号字符数组,则一切都会丢失。

问题:

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

免責聲明:

我知道它可以在所有常见编译器上没有警告。我只想知道我对标准的理解是否正确


为了更好地展示我的问题,让我们考虑一个不同的结构 Elt2,其中sizeof(struct Elt2)<=sizeof(struct Elt),以及

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!

什么会使从字符数组复制与众不同?

代码很好,没有严格的混叠冲突。指向的数据具有有效类型,因此粗体引用文本不适用。这里适用的是你省略的部分,6.5/6的最后一句话:

对于对没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

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

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

Lundin 的答案是正确的;你正在做的事情很好(只要数据对齐并且具有相同的字节序)。

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

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

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

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

相关内容

  • 没有找到相关文章

最新更新