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

Ser*_*sta 6 c malloc struct memcpy language-lawyer

语境:

我正在审查一些代码,这些代码将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;
...
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.提供缓冲区确实包含结构的有效表示 - 假设它已在同一平台上生成,因此没有字节顺序或填充问题 - 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?
...
Run Code Online (Sandbox Code Playgroud)

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

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

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

struct Elt *elt = (struct Elt *) addr;
Run Code Online (Sandbox Code Playgroud)

这确实是一个严格的别名规则违规(并且可能另外带有对齐问题).但是如果memcpy给指定区域一个有效类型的unsigned char数组,那么elt一切都会丢失.

题:

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

免责声明:

我知道它可以在没有所有常见编译器的警告的情况下工作.我只是想知道我对标准的理解是否正确


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

struct Elt2 actual_elt2 = {...};
Run Code Online (Sandbox Code Playgroud)

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

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

虽然罚款动态的(关于它的问题存在):

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!
Run Code Online (Sandbox Code Playgroud)

什么可以使char数组的复制不同?

Lun*_*din 10

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

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

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

使代码安全的原因还在于您数据复制到该结构中.如果你只是指定一个struct Elt*指向同一个内存位置addr,那么你将有一个严格的别名违规和UB.