语境:
我正在审查一些代码,这些代码将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)如果通过具有非字符类型的左值将值存储到没有声明类型的对象中type,然后lvalue的类型成为该访问的对象的有效类型以及不修改存储值的后续访问 . 如果使用memcpy或memmove将值复制到没有声明类型的对象中,或者将其复制为字符类型数组,则该访问的修改对象的有效类型以及不修改该值的后续访问的有效类型是复制值的对象的有效类型(如果有) .
buffer
确实有一个有效的类型,甚至是一个声明的类型:它是 unsigned char
的数组 . 这就是为什么代码使用 memcpy
而不是像单独的别名一样的原因:
struct Elt *elt = (struct Elt *) addr;
这确实是一个严格的别名规则违规(并且可能另外有对齐问题) . 但是如果 memcpy
给了 elt
指向的区域一个有效类型的unsigned char数组,那么一切都会丢失 .
题:
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!
虽然它适合动态的(关于它的问题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 回答
代码很好,没有严格的别名违规 . 指向数据具有有效类型,因此粗体引用文本不适用 . 这里适用的是您遗漏的部分,最后一句6.5 / 6:
因此,指向对象的有效类型变为
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