假设我有两个结构:object
和 widget
:
struct object {
int field;
void *pointer;
};
struct widget {
int field;
void *pointer;
};
还有一个函数:
void consume(struct object *obj)
{
printf("(%i, %p)\n", obj->field, obj->pointer);
}
我知道如果我尝试这样做:
struct widget wgt = {3, NULL};
consume(&wgt);
我会违反 strict aliasing rule ,因此具有未定义的行为。
据我所知,未定义的行为是由于编译器可能以不同方式对齐结构字段:也就是说,填充字段以与地址边界对齐(但从不更改字段顺序,因为顺序保证是遵守标准)。
但是如果这两个结构是 packed 呢? ?他们会有相同的内存布局吗?或者,换句话说,上面的 consume()
是否仍然有未定义的行为(尽管有持续的编译器警告)?
注意:我使用 struct __attribute__((__packed__)) object { ... };
进行打包 (GCC)。
最佳答案
它们很可能具有相同的布局;这将成为编译器 ABI 的一部分。
相关的架构和/或操作系统可能有一个标准的 ABI,它可能包含也可能不包含 packed
的规范。但是编译器将有自己的 ABI 以可预测的方式布置它们,尽管算法可能不会在编译器源代码以外的任何地方精确地写下来。
但是,这并不意味着您的代码是安全的。严格的别名规则适用于指向不同类型的指针,无论它们是否具有相同的布局。
这是一个可以用gcc -O2
编译的例子:
#include <stdio.h>
__attribute__((packed))
struct object {
int field;
void *pointer;
};
__attribute__((packed))
struct widget {
int field;
void *pointer;
};
struct widget *some_widget;
__attribute__((noipa)) // prevent inlining which hides the bug
void consume(struct object *obj)
{
some_widget->field = 42;
int val = obj->field;
printf("%i\n", val);
}
int main(void) {
struct widget wgt = {3, NULL};
some_widget = &wgt;
consume((struct object *)&wgt);
}
您可能希望此代码打印 42
,因为 some_widget
和 obj
都指向 wgt
并且因此 val = obj->field
应该读取由 some_widget->field = 42
编写的相同的 int
。但实际上它打印了 3
。允许编译器假定 obj
和 some_widget
没有别名,因为它们具有不同的类型;所以写和读被认为是独立的,可以重新排序。
在标准层面上,您正在通过左值*some_widget
访问对象wgt
,其有效类型为struct widget
其类型为 struct object
。这些类型不兼容,因为它们具有不同的标签(widget
vs object
),因此行为未定义。
关于c - 打包的相同结构是否保证具有相同的内存布局?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73140420/