当 A 和 B 为 "the same" 时断言 (sizeof(A) == sizeof(B)) 是否安全?
Is it safe to assert(sizeof(A) == sizeof(B)) when A and B are "the same"?
假设我有两个 类,我希望它们具有完全相同的内存布局:
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?
问题是由 触发的。天真地我不希望任何断言失败,但这是有保证的吗?
您断言可能为假的唯一情况是包装标准不同。否则断言必须为真。
编译器只有结构定义来计算成员偏移量,因此除非布局一致,否则您将无法访问该结构。
标准中的任何内容都不会禁止这样一种实现,即标识曾经用作联合的一部分的所有结构,并在未以这种方式使用的任何结构的每个元素之后添加随机数量的填充。另一方面,如果实现可以处理的标记数量,则没有任何东西可以禁止实现以任意方式运行,也没有任何东西可以禁止实现施加一个限制。
所有这些事情都属于标准允许符合标准的实现做的事情,但是即使标准允许,通常也应该期望高质量的实现避免做这些事情。该标准没有努力禁止实现做傻事,也没有猜测某些专门的实现是否有充分的理由以非典型方式处理某些事情。相反,它期望编译器作者将尝试满足其客户的需求,无论标准是否要求他们这样做。
一个人为的反例:
#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));
是的,标准保证断言是安全的。这里的相关术语是布局兼容。
该标准分两部分定义了该术语。首先它定义了什么 a common initial sequence of data members is (but only for 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
这只是它们各自的第一个成员。如果通用初始布局只是整个 class.
,则它将结构定义为 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
不在标准涵盖的范围内。
假设我有两个 类,我希望它们具有完全相同的内存布局:
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?
问题是由
您断言可能为假的唯一情况是包装标准不同。否则断言必须为真。
编译器只有结构定义来计算成员偏移量,因此除非布局一致,否则您将无法访问该结构。
标准中的任何内容都不会禁止这样一种实现,即标识曾经用作联合的一部分的所有结构,并在未以这种方式使用的任何结构的每个元素之后添加随机数量的填充。另一方面,如果实现可以处理的标记数量,则没有任何东西可以禁止实现以任意方式运行,也没有任何东西可以禁止实现施加一个限制。
所有这些事情都属于标准允许符合标准的实现做的事情,但是即使标准允许,通常也应该期望高质量的实现避免做这些事情。该标准没有努力禁止实现做傻事,也没有猜测某些专门的实现是否有充分的理由以非典型方式处理某些事情。相反,它期望编译器作者将尝试满足其客户的需求,无论标准是否要求他们这样做。
一个人为的反例:
#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));
是的,标准保证断言是安全的。这里的相关术语是布局兼容。
该标准分两部分定义了该术语。首先它定义了什么 a common initial sequence of data members is (but only for 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
这只是它们各自的第一个成员。如果通用初始布局只是整个 class.
如果您有两种不同的布局兼容类型的实例,如上例中的 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
不在标准涵盖的范围内。