C/C++ 结构内存布局等效
C/C++ Struct memory layout equivalency
考虑以下 C 结构和 C++ 结构声明:
extern "C" { // if this matters
typedef struct Rect1 {
int x, y;
int w, h;
} Rect1;
}
struct Vector {
int x;
int y;
}
struct Rect2 {
Vector pos;
Vector size;
}
Rect1
和 Rect2
对象的内存布局是否始终相同?
具体来说,我可以安全地从 Rect2*
到 Rect1*
reinterpret_cast
并假设 Rect2
中的所有四个 int
值对象与 Rect1
?
中的四个 int
一对一匹配
如果我将 Rect2
更改为非 POD 类型,例如通过添加构造函数?
是的,它们将永远相同。
您可以在此处 运行 尝试以下示例 cpp.sh
它运行如您所愿。
// Example program
#include <iostream>
#include <string>
typedef struct Rect1 {
int x, y;
int w, h;
} Rect1;
struct Vector {
int x;
int y;
};
struct Rect2 {
Vector pos;
Vector size;
};
struct Rect3 {
Rect3():
pos(),
size()
{}
Vector pos;
Vector size;
};
int main()
{
Rect1 r1;
r1.x = 1;
r1.y = 2;
r1.w = 3;
r1.h = 4;
Rect2* r2 = reinterpret_cast<Rect2*>(&r1);
std::cout << r2->pos.x << std::endl;
std::cout << r2->pos.y << std::endl;
std::cout << r2->size.x << std::endl;
std::cout << r2->size.y << std::endl;
Rect3* r3 = reinterpret_cast<Rect3*>(&r1);
std::cout << r3->pos.x << std::endl;
std::cout << r3->pos.y << std::endl;
std::cout << r3->size.x << std::endl;
std::cout << r3->size.y << std::endl;
}
- 我会这么认为,但我也认为
Rect2::pos
和 Rect2::size
之间可以(合法地)填充。因此,为了确保,我会将特定于编译器的属性添加到 "pack" 字段,从而保证所有 int
都是相邻且紧凑的。这不是关于 C 与 C++ 的关系,更多的是关于在使用两种语言进行编译时您可能会使用两个 "different" 编译器这一事实,即使这些编译器来自一个供应商。
- 使用
reinterpret_cast
将指向一种类型的指针转换为指向另一种类型的指针,您很可能违反了"strict aliasing"规则。假设你之后取消引用指针,在这种情况下你会这样做。
- 添加构造函数不会改变布局(尽管它会使 class 成为非 POD),但在两个字段之间添加访问说明符
private
可能会改变布局(实际上,不仅在理论上)。
Are the memory layouts of Rect1 and Rect2 objects always identical?
是的。只要满足某些明显的要求,就可以保证它们是相同的。这些明显的要求是关于目标 platform/architecture 在对齐和字长方面相同。换句话说,如果您愚蠢到为不同的目标平台(例如,32 位与 64 位)编译 C 和 C++ 代码并尝试混合使用它们,那么您就会遇到麻烦,否则,您不必担心,C++ 编译器基本上需要生成与在 C 中相同的内存布局,并且 ABI 对于给定的字长和对齐在 C 中是固定的。
Specifically, can I safely reinterpret_cast from Rect2* to Rect1* and assume that all four int values in the Rect2 object are matched one on one to the four ints in Rect1?
是的。这是第一个答案的结果。
Does it make a difference if I change Rect2 to a non-POD type, e.g. by adding a constructor?
没有,或者至少,没有了。唯一重要的是 class 仍然是 standard-layout class,它不受构造函数或任何其他非虚拟成员的影响。这自 C++11 (2011) 标准以来有效。在此之前,语言是关于 "POD-types",正如我刚刚为标准布局提供的 link 中所解释的那样。如果你有一个 C++11 之前的编译器,那么它很可能仍然按照与 C++11 标准相同的规则工作(C++11 标准规则(用于标准布局和普通类型)基本上是编写以匹配所有编译器供应商已经完成的工作)。
对于像您这样的标准布局 class,您可以轻松检查结构成员如何从结构开始定位。
#include <cstddef>
int x_offset = offsetof(struct Rect1,x); // probably 0
int y_offset = offsetof(struct Rect1,y); // probably 4
....
pos_offset = offsetof(struct Rect2,pos); // probably 0
....
考虑以下 C 结构和 C++ 结构声明:
extern "C" { // if this matters
typedef struct Rect1 {
int x, y;
int w, h;
} Rect1;
}
struct Vector {
int x;
int y;
}
struct Rect2 {
Vector pos;
Vector size;
}
Rect1
和Rect2
对象的内存布局是否始终相同?具体来说,我可以安全地从
Rect2*
到Rect1*
reinterpret_cast
并假设Rect2
中的所有四个int
值对象与Rect1
? 中的四个 如果我将
Rect2
更改为非 POD 类型,例如通过添加构造函数?
int
一对一匹配
是的,它们将永远相同。 您可以在此处 运行 尝试以下示例 cpp.sh 它运行如您所愿。
// Example program
#include <iostream>
#include <string>
typedef struct Rect1 {
int x, y;
int w, h;
} Rect1;
struct Vector {
int x;
int y;
};
struct Rect2 {
Vector pos;
Vector size;
};
struct Rect3 {
Rect3():
pos(),
size()
{}
Vector pos;
Vector size;
};
int main()
{
Rect1 r1;
r1.x = 1;
r1.y = 2;
r1.w = 3;
r1.h = 4;
Rect2* r2 = reinterpret_cast<Rect2*>(&r1);
std::cout << r2->pos.x << std::endl;
std::cout << r2->pos.y << std::endl;
std::cout << r2->size.x << std::endl;
std::cout << r2->size.y << std::endl;
Rect3* r3 = reinterpret_cast<Rect3*>(&r1);
std::cout << r3->pos.x << std::endl;
std::cout << r3->pos.y << std::endl;
std::cout << r3->size.x << std::endl;
std::cout << r3->size.y << std::endl;
}
- 我会这么认为,但我也认为
Rect2::pos
和Rect2::size
之间可以(合法地)填充。因此,为了确保,我会将特定于编译器的属性添加到 "pack" 字段,从而保证所有int
都是相邻且紧凑的。这不是关于 C 与 C++ 的关系,更多的是关于在使用两种语言进行编译时您可能会使用两个 "different" 编译器这一事实,即使这些编译器来自一个供应商。 - 使用
reinterpret_cast
将指向一种类型的指针转换为指向另一种类型的指针,您很可能违反了"strict aliasing"规则。假设你之后取消引用指针,在这种情况下你会这样做。 - 添加构造函数不会改变布局(尽管它会使 class 成为非 POD),但在两个字段之间添加访问说明符
private
可能会改变布局(实际上,不仅在理论上)。
Are the memory layouts of Rect1 and Rect2 objects always identical?
是的。只要满足某些明显的要求,就可以保证它们是相同的。这些明显的要求是关于目标 platform/architecture 在对齐和字长方面相同。换句话说,如果您愚蠢到为不同的目标平台(例如,32 位与 64 位)编译 C 和 C++ 代码并尝试混合使用它们,那么您就会遇到麻烦,否则,您不必担心,C++ 编译器基本上需要生成与在 C 中相同的内存布局,并且 ABI 对于给定的字长和对齐在 C 中是固定的。
Specifically, can I safely reinterpret_cast from Rect2* to Rect1* and assume that all four int values in the Rect2 object are matched one on one to the four ints in Rect1?
是的。这是第一个答案的结果。
Does it make a difference if I change Rect2 to a non-POD type, e.g. by adding a constructor?
没有,或者至少,没有了。唯一重要的是 class 仍然是 standard-layout class,它不受构造函数或任何其他非虚拟成员的影响。这自 C++11 (2011) 标准以来有效。在此之前,语言是关于 "POD-types",正如我刚刚为标准布局提供的 link 中所解释的那样。如果你有一个 C++11 之前的编译器,那么它很可能仍然按照与 C++11 标准相同的规则工作(C++11 标准规则(用于标准布局和普通类型)基本上是编写以匹配所有编译器供应商已经完成的工作)。
对于像您这样的标准布局 class,您可以轻松检查结构成员如何从结构开始定位。
#include <cstddef>
int x_offset = offsetof(struct Rect1,x); // probably 0
int y_offset = offsetof(struct Rect1,y); // probably 4
....
pos_offset = offsetof(struct Rect2,pos); // probably 0
....