2013-07-23 97 views
9

我從SO上發佈的幾個問題閱讀了關於這一段的內容。爲什麼不保證memcpy對非POD類型安全?

我不能完全弄清楚爲什麼memcpy不能保證對非POD類型安全。我的理解是,memcpy只是一個按位拷貝。

下面是從標準

POD類型T的報價對於任何對象(比基類子對象等),對象是否保持T類型的有效值, 底層字節(1.7)構成的對象可以被拷貝到charunsigned char 0.41)如果charunsigned char陣列的內容 被複制回對象的陣列,該對象隨後應保持其原始 值。

# define N sizeof (T) 
char buf[N]; 
T obj ; // obj initialized to its original value 
std :: memcpy (buf , & obj , N); // between these two calls to std::memcpy, 
           // obj might be modified 
std :: memcpy (& obj , buf , N); // at this point, each subobject of obj of 
           // scalar type holds its original value 
+3

如果定義了複製構造函數,而不是按位複製,會發生什麼? –

+4

'memcpy'是一個按位拷貝。非POD類型不一定表現出按位複製語義。 – lapk

+0

但這不是標準所說的......標準說......「對象將隨後保持其原始值」,這在記憶非POD類型時是正確的。 – ROTOGG

回答

6

試想擁有一些指針這樣的緩衝類:

class Abc { 
    public: 
    int* data; 
    size_t n; 
    Abc(size_t n) 
    { 
     this->n = n; 
     data = new int[n]; 
    } 

    // copy constructor: 
    Abc(const Abc& copy_from_me) 
    { 
     n = copy_from_me.n; 
     data = new int[n]; 
     memcpy(data, copy_from_me.data, n*sizeof(int)); 
    } 
    Abc& operator=(const Abc& copy_from_me) 
    { 
     n = copy_from_me.n; 
     data = new int[n]; 
     memcpy(data, copy_from_me.data, n*sizeof(int)); 
     return *this; 
    } 

    ~Abc() 
    { 
     delete[] data; 
    } 
} ; 

如果你只是MEMCOPY它的實例之一,你會得到兩個實例指向到同一個緩衝區。如果您在一個實例中修改數據,則會在另一個實例中修改數據。

這意味着你並沒有真正將它克隆到兩個獨立的類中。而且,如果你刪除了這兩個類,那麼緩衝區會從內存中釋放兩次,從而崩潰。 所以這個類必須定義一個拷貝構造函數,你必須使用構造函數來拷貝它。

+1

這實際上證明什麼都沒有。包含指針的結構會發生同樣的情況,struct是POD類型。 – Dariusz

+2

@Dariusz在C++中'struct'就像'class',唯一的區別是'struct'默認爲'public','class'爲'private'。 –

+5

@Dariusz:只是因爲某些東西是一個結構,並不會使它成爲POD。我認爲這篇文章試圖做的一點與三條規則相同:dtor可以嘗試刪除數據指向的內存,當memcpyd導致雙重刪除時,拷貝策略解決的內存做一個深層複製。 – PlasmaHH

18

嘗試按位複製std::shared_ptr<>。你可能會發現你的程序經常會在你的臉上爆炸。

你會遇到這個問題,任何類的複製構造函數做一個比特明智的副本以外的東西。在std::shared_ptr<>的情況下,它將複製指針,但不會增加引用計數,因此您最終將盡早釋放共享對象及其引用計數,然後在複製的shared_ptr嘗試減少釋放時炸掉引用計數。


UPDATE:有人指出,這並不完全回答這個問題,這是公平的,因爲我主要是針對抄襲的shared_ptr到shared_ptr的想法,shared_ptr不爲char [],然後再返回。但是,這個原則依然成立。

如果你按位拷貝一個shared_ptr到一個char [],給shared_ptr分配一個不同的值,然後將char []複製回來,最終結果可能是泄漏一個對象並雙重刪除另一個,即UB。

POD也可能發生這種情況,但這是程序邏輯中的一個錯誤。只要程序能夠理解並容納這樣的事件,按位拷貝回等同於修改過的shared_ptr的POD將是完全有效的。這樣做對於std :: shared_ptr通常不起作用。

+0

我認爲這並沒有回答這個問題。我相信,在將shared_ptr的字節複製到char數組並立即返回之後,shared_ptr將具有其原始值。 OP不想對副本進行任何操作,而是將其複製回來,並想知道爲什麼這些來回等價語義只是爲POD定義的。 (我可以想象垃圾收集方案可能會改變原始指針的值,從而使副本中的指針值無效,但除了可能與POD一起出現的MT問題之外,我認爲我無法想象會出現什麼問題。) –

+0

@ PeterA.Schneider我明白你的觀點,但答案仍然成立。爲了清晰起見,我已更新它。 –

1

假設例如您正在編寫String類。該類的任何實例都應該包含一個指向某些動態分配的數組的指針。如果你memcopy這樣一個實例,那麼這兩個指針是平等的。一個字符串的任何修改都會影響另一個字符串。

+1

在給出的示例中,這不會成爲問題,嘗試恢復對象的原始值,而不是嘗試創建具有相同值的第二個對象。注意這個例子中只有一個T對象。 –

+1

@ r-martinho-fernandes這不是真的。因爲在兩個memcpy調用之間「obj可能被修改」,這包括字符串可能被重新分配,包括釋放不再使用的內存。在第二次赦免之後,原來的(現在無效的)指針將被恢復並等待破壞。 –

+0

@umläute在答案中提到! –

3

一般來說,問題是對象不僅引入數據,還引入行爲

通過複製數據手工我們可以打破對象,它可以依靠拷貝構造函數的內在行爲。

一個很好的例子是任何共享唯一指針 - 通過複製它,我們打破了「交易」,我們與一流的製作時,我們使用它。

不管複製過程是語義正確與否,這樣做背後的想法是錯誤的,違反了對象的編程範式。

示例代碼:

/** a simple object wrapper around a pthread_mutex 
*/ 
class PThreadMutex 
{ 
    public: 
    /** locks the mutex. Will block if mutex is already locked */ 
    void lock(); 

    /** unlocks the mutex. undefined behavior if mutex is unlocked */ 
    void unlock(); 

    private: 
    pthread_mutex_t m_mutex; 

}; 

/** a simple implementation of scoped mutex lock. Acquires and locks a Mutex on creation, 
* unlocks on destruction 
*/ 
class ScopedLock 
{ 
    public: 
    /** constructor specifying the mutex object pointer to lock 
    * Locks immediately or blocks until lock is free and then locks 
    * @param mutex the mutex pointer to lock 
    */ 
    ScopedLock (PThreadMutex* mutex); 

    /** default destructor. Unlocks the mutex */ 
    ~ScopedLock(); 

    /** locks the mutex. Will block if mutex is already locked */ 
    void unlock(); 


    private: 

    PThreadMutex* m_mutex; 

    // flag to determine whether the mutex is locked 
    bool m_locked; 

    // private copy constructor - disable copying 
    ScopedLock(ScopedLock &mutex) { (void)mutex; /* to get rid of warning */ }; 

}; 

如果複製ScopedLock類,手動解鎖,然後恢復初始值和執行構造另一個解開它會導致一個未定義的行爲(或至少EPERM錯誤的析構函數)。

+0

在給出的例子中它會被打破嗎? (不,它不會:在那裏只有一個T對象,所以顯然它的ID是唯一的) –

+0

@ R.MartinhoFernandes事實上它不會,但我想不出任何這樣做。在該對象上沒有明確的操作,並且在那之後發生的唯一的事情就是析構函數調用。 – Dariusz

+0

有「這兩個調用之間std :: memcpy,obj可能會被修改」 –

1

C++ 11 note:問題中的引用是規則的一個相當舊的版本。由於C++ 11,要求是平凡能夠複製POD弱得多。


memcpy可以從任何對象使用。你得到一個對象的按位圖像。

如果對象不是POD,則圖像不能使用,好像它是同一類型與原始對象,這是因爲壽命規則要求初始化首先完成。

在這種情況下,圖像只是一堆字節。這可能仍然有用,例如,隨着時間的推移檢測對象的內部表示的變化,但只有對字節有效的操作(比如兩個圖像之間的比較)纔是合法的,而不是需要原始類型的對象的操作。

+0

你可以構造一個具體的例子,來回地往返char數組使對象失效或導致UB? (假設單線程且沒有中間代碼。) –

+0

@ PeterA.Schneider:您提到的是一個更具限制性的場景。儘管如此,從完全或部分爲「const」的對象中讀取和寫回,會產生未定義的行爲,並可能在實際實現中導致硬件異常。 –