假设我有两个类,它们的内存布局应该完全相同:
struct A {
int x;
int y;
};
/* possibly more code */
struct B {
int a;
int b;
};
该标准中是否有任何内容可以保证我可以安全地static_assert(sizeof(A) == sizeof(B))
?
作为较弱的变体,请考虑
struct C {
int a;
};
static_assert( sizeof(A) >= sizeof(C) ); // can this ever fail?
static_assert( sizeof(A) > sizeof(C) ); // can this ever fail?
问题由this one触发。我天真地希望不会有任何断言失败,但是可以保证吗?
答案 0 :(得分:4)
标准中的任何内容都不会禁止实现一种方法,该方法可以标识曾经用作联合的一部分的所有结构,并在未以这种方式使用的任何结构的每个元素之后添加随机量的填充。另一方面,如果一个实现可以处理的标签数量不多,则任何事情都不会阻止该实现的行为,也不能阻止一个实现施加一个限制。
所有这些事情都属于标准允许一致的实现执行的事情的类别,但是即使标准允许,通常也应该指望哪些质量好的实现不能执行。该标准没有做出任何努力来禁止实现者执行愚蠢的事情,也没有猜测某些专门的实现者是否有充分的理由以非典型的方式处理某些事情。相反,它希望编译器编写者将尝试满足客户的需求,无论标准是否要求他们这样做。
答案 1 :(得分:3)
人为设计的 反例:
#include <stdint.h>
struct A {
int32_t a;
int64_t b;
};
#pragma pack(4)
struct B {
int32_t a;
int64_t b;
};
static_assert(sizeof(A) == sizeof(B));
在64位Linux中使用g++
进行编译会产生:
a.cc:15:1: error: static assertion failed
static_assert(sizeof(A) == sizeof(B));
答案 2 :(得分:1)
是的,标准保证断言是安全的。此处的相关术语是布局兼容。
标准将该术语定义为两部分。首先,它定义了数据成员 common initial sequence 是什么(但仅限于 standard-layout structs):它是两个结构之间等效的数据成员序列。该标准包含一个示例,但我将使用一个略有不同的示例来避免一些技术问题:
struct A { int a; char b; };
struct B { int b1; char b2; };
struct C { int x; int y; };
在该示例中,A
和 B
的共同初始布局都是它们的成员,而对于 A
和 C
,它只是它们各自的第一个成员。如果公共初始布局只是整个类,则它将结构定义为 layout compatible。
如果您有两种不同的布局兼容类型的实例,例如上面示例中的 A
和 B
,您*可以*假设它们具有相同的大小:
static_assert(sizeof(A) == sizeof(B));
但是,您*不能*(理论上)在不调用未定义行为的情况下在它们之间进行转换,因为这违反了别名规则:
A a{1, 'a'};
B* b = reinterpret_cast<B*>(&a); // undefined behaviour!
do_something_with(b);
根据通常的 const
/volatile
规则以及关于数据成员无关紧要的规则(请参阅 When is a type in c++11 allowed to be memcpyed?),您可以使用 memcpy
在布局兼容的结构之间获取数据。当然,正如当前最佳答案所暗示的那样,成员之间的填充不可能随机不同。
A a{1, 'a'};
B b;
memcpy(&b, &a, sizeof(b)); // copy from a to b
do_something_with(b);
如果 do_something_with
通过引用获取其参数并修改它,那么您需要从 b
复制回 a
以反映那里的效果。在实践中,这通常会被优化为您期望上述演员做的事情。
atomsymbol 的回答给出了一个似乎与上述所有内容相矛盾的例子。但是您询问了标准中的内容,而影响填充的 #pragma
不在标准涵盖的范围内。
答案 3 :(得分:-4)
唯一可以断言的实例是包装标准不同时。否则,断言必须为真。
编译器仅具有结构定义来计算成员偏移量,因此,除非布局一致,否则您将无法访问该结构。