C++中构造函数清零空类并继承:是否合法?行为是否明确?
空类构造函数中使用memset的安全性与标准行为分析
不少库会在构造函数中用std::memset将对象清零,甚至对空类也执行这类操作。这种操作是否安全?尤其是当继承这类空类时?
示例代码
#include <iostream> #include <cstring> struct A { char a; }; struct B { B() { std::memset( this, 0, sizeof(B) ); } }; struct C : A, B {}; static_assert( sizeof(C) == 1 ); int main() { std::cout << (int)C{ {1}, {} }.a << (int)C{ {1}, B{} }.a; }
不同编译器的输出与警告
- GCC输出:
00 - Clang输出:
01 - MSVC输出:
11,并发出警告:
warning C4789: buffer '' of size 1 bytes will be overrun; 1 bytes will be written starting at offset 1
核心问题分析
1. 空基类优化(EBO)的影响
C++标准允许编译器对空基类进行优化:当派生类已经有非空的基类或成员时,空基类的子对象可以不占用额外存储空间(即大小为0),以此减少派生类的总大小。示例中struct C : A, B{}的sizeof(C)为1,就是因为B作为空基类被优化,没有额外占用空间。
但注意:sizeof(B)作为类型B的大小始终是1(C++要求每个对象必须有唯一地址,空类的大小至少为1),这和空基类子对象在派生类中的存储大小是两回事。
2. memset操作的风险
在B的构造函数中,std::memset(this, 0, sizeof(B))试图从this指向的地址(即B基类子对象的地址)开始写入1字节。但由于EBO的存在,B子对象在C中实际占用的存储空间为0,此时写入1字节会超出B子对象的存储区域,覆盖到A子对象的内存空间——这属于C++标准定义的未定义行为。
3. 不同编译器输出的合理性
未定义行为意味着标准不规定具体结果,编译器可以自由处理:
- GCC中,
B子对象与A子对象地址重叠,memset直接覆盖了A::a,所以输出00; - Clang中,第一种初始化场景下memset覆盖了
A::a,但第二种场景下拷贝B{}时未影响A::a,输出01; - MSVC中,
B子对象被分配到偏移1的位置(但sizeof(C)仍为1),memset操作触发缓冲区溢出警告,且未覆盖A::a,输出11。
这些结果都符合标准,因为未定义行为的结果没有对错之分。
结论
- 空类构造函数中使用
memset(this, 0, sizeof(类))作为独立对象时是安全的,但作为基类被继承时存在严重风险,会触发未定义行为; - 示例中的行为属于未定义行为,三种编译器的处理都符合C++标准,没有“正确”之分;
- 避免在构造函数中用
memset处理类对象,尤其是可能被作为基类的空类——推荐直接初始化成员变量,而非依赖底层内存操作。
内容的提问来源于stack exchange,提问作者Fedor
相关产品推荐
相关产品推荐

