我们都知道memcpy、memset、memcmp等内存操作系列函数可帮助我们完成数据的初始化,数据的复制、数据的拷贝等工作。但你知道为什么可这么做吗?
究其原因,是因为在C语言中,无论是内置数据类型、还是自定义数据类型都是POD对象。对于POD对象其内存模型都是可知的、透明的。
什么样的数据对象是POD对象?在C++中,习惯把传统的C风格的struct叫做POD(Plain Old Data)对象。一般来说,POD对象应该具备下述特征:
(1)对于POD类型T的对象,不管这个对象是否拥有类型T的有效值,如果将该对象的底层字节序列复制到一个字符数组中,再将其复制回对象,那么该对象的值与原始值一样。
(2)对于任意的POD类型T,如果两个T指针分别指向两个不同的对象obj1和obj2,如果用memcpy库函数把obj1的值复制到obj2,那么obj2将拥有与obj1相同的值。
简言之,针对POD对象,其二进制内容是可以随便复制的,在任何地方,只要其二进制内容在,就能还原出正确无误的POD对象。对于任何POD对象,都可以使用memset()函数或者其他类似的内存操作函数。我们看POD对象,使用memcpy等函数的例子:
#include "stdafx.h"
#include <cstring>
typedef struct tagPERSON
{
char szName[16];
int nAge;
bool bGender;
}PERSON;
void PrintInfo(PERSON * p)
{
printf("%s,%d,%s/r/n", p-> szName , p-> nAge , (p-> bGender ? "男" : "女"));
}
int main()
{
PERSON p1 = {"佟湘玉", 28, false};
PERSON p3 = {"白展堂", 26, true};
PrintInfo (&p1);
PrintInfo (&p3);
char szBytes[sizeof(PERSON)];
memcpy(szBytes, &p1, sizeof(PERSON));
PERSON p2 = {0};
memset(&p2, 0, sizeof(PERSON));
PrintInfo (&p2);
memcpy(&p2, szBytes, sizeof(PERSON));
PrintInfo (&p2);
memcpy(&p2, &p3, sizeof(PERSON));
PrintInfo (&p2);
return 0;
}
但对于C++中的非POD对象,我们再使用memset等函数就无法奏效了。这是因为对于POD对象,我们可通过对象的基地址和数据成员的偏移量获取数据成员的地址,这是POD对象遵循的最基本的内存布局假设。但是C++标准中并未对非POD对象的内存布局做任何的假设,不同的编译器在实现非POD对象内存布局是采用不同的布局方案。这是memset系列函数无法奏效的根本原因。
那C++为何引入非POD对象呢?这究其原因还是C++最主要的特征(动态的多态)引起的。支持动态的多态的类都存在虚函数,而虚函数实现的机制在于虚函数表,没有虚函数表动态的多态无法运行。而这个虚函数表必须放在类对象体中,也就是和类对象的数据存放在一块。这就导致了类对象中的数据不采用连续方式存储,被分割成不同的部分。所以,针对非POD对象,贸然的使用memcpy和memset等函数往往会导致不可预料的后果。
请谨记
- 区分哪些对象是POD,对于POD对象你可以大胆的使用memcpy和memset函数。但对于非POD对象,我建议你不要使用memcpy和memset等函数。
- C++中的对象可能是POD的也可能是非POD的。因此在C++中使用memset和memcpy等函数要足够小心。
|