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

时间:2018-02-01 09:58:25

标签: c struct malloc language-lawyer memcpy

上下文:

我正在审查一些代码,这些代码将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

中说
  

访问其存储值的对象的有效类型是声明的类型   object,如果有的话.87)如果一个值存储到一个没有声明类型的对象中   lvalue的类型不是字符类型,那么左值的类型就变成了   该访问的对象的有效类型以及不修改的后续访问   储存的价值。 如果将值复制到未使用声明类型的对象中   memcpy或memmove,或者被复制为字符类型数组,然后是有效类型   用于该访问的修改对象以及不修改该访问的后续访问   value是从中复制值的对象的有效类型,如果它有一个

buffer确实有一个有效的类型,甚至是一个声明的类型:它是一个unsigned char的数组。这就是为什么代码使用memcpy而不是像#:p>这样的单独混叠的原因

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

这确实是一个严格的别名规则违规(并且可能另外带有对齐问题)。但是如果memcpy已经为elt指向的区域提供了有效类型的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!

虽然它适用于动态的(问题there):

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数组的复制不同?

2 个答案:

答案 0 :(得分:10)

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

  

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

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

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

答案 1 :(得分:0)

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

我想要注意的是,这不是C语言规范的结果,因为它是硬件的工作原理。因此,没有一个权威的答案。 C语言规范定义了语言的工作方式,而不是语言在不同系统上的编译或实现方式。

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

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

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