memcpy()、memset()、memcmp()等這些內存操作函數經常會幫我們完成一些數據復制、賦值等操作。因為在C語言中,無論是內置類型,還是自定義的結構類型(struct),其內存模型對於我們來說都是可知的、透明的。所以,我們可以對該對象的底層字節序列一一進行操作,簡單而有效。代碼片段如下所示:
- struct STUDENT
- {
- char _name[32];
- int _age;
- bool _gender;
- };
- STUDENT a = {"Li Lei", 20, true};
- STUDENT b = {"Han MeiMei", 19, false};
- int len = sizeof(STUDENT);
- STUDENT c;
- memset(&c, 0, len);
- memcpy(&c, &a, len);
- char *data = (char*)malloc(sizeof(char)*len);
- memcpy(data, &b, len);
在C++中,我們把傳統C風格的數據類型叫做POD(Plain Old Data)對象,即一種古老的純數據。在C的世界里根本沒有POD這一概念,因為C的所有對象都是POD。一般來說,POD對象應該滿足如下特性:其二進制內容是可以隨意復制的,無論在什么地方,只要其二進制內容存在,我們就能准確無誤地還原出POD對象。正是由於這個原因,對於任何POD對象,我們都可以放心大膽地使用memset()、memcpy()、memcmp()等函數對對象的內存數據進行操作。
然而在C++中,每個人都要十二分的注意了。因為C++的對象可能並不是一個POD,所以我們無法像在C中那樣獲得該對象直觀簡潔的內存模型。對於POD對象,我們可以通過對象的基地址和數據成員的偏移量獲得數據成員的地址。但是C++標准並未對非POD對象的內存布局做任何定義,對於不同的編譯器,其對象布局是不同的。而在C語言中,對象布局僅僅會受到底層硬件系統差異的影響。
針對非POD對象,其序列化會遇到一定的障礙:由於對象的不同部分可能存在於不同的地方,因而無法直接復制,只能通過手工加入序列化操作代碼來處理對象數據,很麻煩。但是針對POD對象,這一切將變得不再困難:從基地址開始,直接按對象的大小復制數據,或傳輸,或存儲,隨意處理。
為什么C++中的對象有可能不是一個POD呢?這還要從C++的重要特征之一—動多態說起。動多態的一個基本支撐技術就是虛函數。在使用虛函數時,類的每一次繼承都會產生一個虛函數表(vtable),其中存放的是指向虛函數的指針。這些虛函數表必須存放在對象體中,也就是和對象的數據存放在一起。因而,對象數據在內存里並不是以連續的方式存放的,而是被分割成了不同的部分,甚至“身首異處”。既然對象數據不再集中在一起,如果此時再貿然使用memcpy()、memset()函數,那么所帶來的后果將不可預計。
請記住:
要區分哪些數據對象是POD,哪些是非POD。由於非POD對象的存在,在C++中使用memcpy()系列函數時要保持足夠的小心。