说明:鉴于C和C++标准都收费且死贵,下面列出的标准都是open-std.org上的草案,理论上这是最后一版送审的草案,跟正式版区别不大。
之前我对一个C++对象初始化的时候直接用了memset
,导致了异常。随后我意识到对象还包含了一些其它的信息,全部设为0后肯定导致异常,不能被memset。但是struct在C++中是类的一种,按说它也不能被memset,但这与C就不兼容了。
于是我去查了半天标准,得出结论:平凡可复制的类型可以memset,即使它是对象。而对非平凡可复制的对象执行memset将导致未定义行为。
memset
的函数签名是:
void *memset(void *s, int ch, size_t n);
其中s
是要写入的目标,ch
是要写入的数据,n
是字节数。
C++标准直接把我踢过去看C标准了;而C标准指明了memset
的作用是按字节简单填充:
Synopsis
#include <string.h>
void *memset(void *s, int c, size_t n);
Description
The memset function copies the value of c (converted to an unsigned char) into
each of the first n characters of the object pointed to by s.
Returns
The memset function returns the value of s.
要注意的是虽然ch
的类型是int
,但是实际上会被强制转换为unsigned char
。
而关于对象能否被按字节填充,C++ 11标准提到:
For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object
holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array
of char or unsigned char.40 If the content of the array of char or unsigned char is copied back into the
object, the object shall subsequently hold its original value.
也就是说,“平凡可复制”的类型的对象可以被转换为unsigned char
或者char
数组,显然可以被按照字节填充;而不是“平凡可复制”的类型的对象则没有提及。
关于“平凡可复制”的类,C++ 11是这样规定的:
A trivially copyable class is a class that:
— has no non-trivial copy constructors (12.8),
— has no non-trivial move constructors (12.8),
— has no non-trivial copy assignment operators (13.5.3, 12.8),
— has no non-trivial move assignment operators (13.5.3, 12.8), and
— has a trivial destructor (12.4).
A trivial class is a class that has a trivial default constructor (12.1) and is trivially copyable.
[ Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base
classes. — end note ]
而关于“平凡可复制”的类型,C++ 11是这样规定的:
Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types. Scalar types, POD
classes (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called POD types. Scalar types, trivially copyable class types (Clause 9), arrays of such types, and cv-qualified versions of these types (3.9.3) are collectively called trivially copyable types. Scalar types, trivial class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called trivial types. Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types.
也就是说,我们可以得到“平凡可复制”类型的列表:
- 标量类型,包括:
- 算术类型,包括
- 整型数据:
- bool
- char
- short
- int
- long
- long long
- 以上类型加上signed或者unsigned修饰产生的类型
- 实型数据:
- float
- double
- long double
- 整型数据:
- 枚举
- 各种指针
- std::nullptr_t(这是nullptr的类型)
- 以上类型加上const或volatile修饰产生的类型
- 算术类型,包括
- 平凡可复制的类,即满足下列条件的类:
- 没有“不平凡的”拷贝构造函数
- 没有“不平凡的”移动构造函数
- 没有“不平凡的”拷贝赋值运算符重载
- 没有“不平凡的”移动赋值运算符重载
- 有一个平凡构造函数
- 平凡拷贝/移动构造函数的定义是:
- 构造函数没有被用户定义(即,是自动合成的)
- 没有被删除
- 类没有虚函数或虚基类
- 被选中用于复制或移动其直接基类子对象(即派生类中直接继承的那些基类部分)的构造函数是平凡的
- 成员变量如果有对象,其被选中的拷贝/移动构造函数是平凡的
- 平凡构造函数的定义是:
- 是自动合成的
- 没有被删除
- 类没有虚函数或虚基类
- 被选中用于构造其直接基类子对象(即派生类中直接继承的那些基类部分)的构造函数是平凡的
- 成员变量如果有对象,其被选中的构造函数是平凡的
- 以上类型构成的数组
- 以上类型加上const或volatile修饰产生的类型
上述类型是可以memset的,而对于除上述类型以外的类型memset,虽然标准无规定,但它破坏了对象的正确构造和析构过程,应当视为未定义行为。