2009-12-29 119 views
36

我聽到一種說法,C++程序員應該避免的memset,C++程序員應該避免memset嗎?

class ArrInit { 
    //! int a[1024] = { 0 }; 
    int a[1024]; 
public: 
    ArrInit() { memset(a, 0, 1024 * sizeof(int)); } 
}; 

所以考慮上面的代碼,如果你不使用的memset,你怎麼能讓以0?怎麼啦A [1..1024]與memset在C + +?

謝謝。

+3

你可以給出你爲什麼認爲不應該在C++中執行memset的原因嗎?我不知道爲什麼memset會導致C++中的任何問題。如果我錯了,請糾正我。謝謝! – Jay 2009-12-29 17:55:04

+0

他可能在「不要使用memset來清除類對象」的上下文中聽到它。 – 2009-12-29 18:48:07

+2

@Jay:以上都可以。但是使用memset來清零類對象本身(不僅僅是一個成員)不是一個好主意。如果對象包含具有構造函數的成員(執行一些初始化操作),那麼這尤其具有代表性。 – 2009-12-29 19:01:22

回答

44

問題不在於使用內建類型上的memset(),而是在類(又名非POD)類型上使用它們。這樣做幾乎總會做錯誤的事情,並經常做出致命的事情 - 例如,它可能會踐踏虛擬函數表指針。

+1

你能否添加一個使用memset錯誤的例子? – 2009-12-29 18:25:53

+6

在任何具有虛函數的類上使用memset很可能是壞的。 – 2009-12-29 18:40:26

+0

@Otto:因爲sizeof(class)會將虛擬函數表指針視爲一個數據成員。 – Jichao 2009-12-29 18:50:37

23

零初始化應該是這樣的:

class ArrInit { 
    int a[1024]; 
public: 
    ArrInit(): a() { } 
}; 

至於用memset,有一對夫婦的方式,使使用更強大的(與所有這些功能):避免硬編碼陣列的尺寸和類型:

memset(a, 0, sizeof(a)); 

對於額外的編譯時檢查,也可以確保a確實是一個數組(所以sizeof(a)纔有意義):

template <class T, size_t N> 
size_t array_bytes(const T (&)[N]) //accepts only real arrays 
{ 
    return sizeof(T) * N; 
} 

ArrInit() { memset(a, 0, array_bytes(a)); } 

但是對於非字符類型,我想可以使用它來填充的唯一值是0,並且零初始化應該已經可以以某種方式提供。

+0

如果要使用非零初始化數組,請怎麼辦? – Jichao 2009-12-29 18:31:19

+0

您可以在花括號中放入任何值(例如ArrInit():a(){5}),並使用該值初始化該數組。 – Pace 2009-12-29 18:40:34

+1

你確實意識到我所要做的就是將你的例子中的'int'改爲某個具有虛函數的類,並且你的代碼可能會消滅vptr,不是嗎?你正在解釋如何以一種更安全的方式引發災難。 – 2009-12-29 18:45:33

-3

在C++中,你應該使用新的。在你的例子中使用簡單數組的情況下,使用它沒有實際問題。但是,如果你有一個類的數組並且使用memset來初始化它,那麼你就不會正確地構造這些類。

考慮一下:

class A { 
    int i; 

    A() : i(5) {} 
} 

int main() { 
    A a[10]; 
    memset (a, 0, 10 * sizeof (A)); 
} 

爲每個元素的構造函數將被調用,所以成員變量我不會設置爲5。如果使用新的來代替:

A a = new A[10]; 

比數組中的每個元素將其構造函數調用,我將被設置爲5.

+0

我錯過了將它初始化爲零的問題,並專注於memset和new之間的區別。 – Casey 2009-12-29 18:01:17

+1

@Casey:在我的g ++編譯器中,a a [1]確實調用構造函數,並且memeber變量i將被設置爲5. – Jichao 2009-12-29 18:26:40

+3

'a a [10] = new A [10];'無效的C++ 。你似乎把C++與另一種語言混淆了。 – 2009-12-29 19:35:34

0

您的代碼很好。我認爲在memset是危險的C++中唯一的一次是當你按照如下方式做一些事情時:
YourClass instance; memset(&instance, 0, sizeof(YourClass);

我相信它可能會清除編譯器創建的實例中的內部數據。

8

這是「壞」,因爲你沒有實現你的意圖。

您的意圖是將數組中的每個值都設置爲零,並且您編程的內容是將原始內存區域設置爲零。是的,這兩件事情具有相同的效果,但只需編寫代碼來清零每個元素就會更清楚。

此外,它可能沒有更高效。

class ArrInit 
{ 
public: 
    ArrInit(); 
private: 
    int a[1024]; 
}; 

ArrInit::ArrInit() 
{ 
    for(int i = 0; i < 1024; ++i) { 
     a[i] = 0; 
    } 
} 


int main() 
{ 
    ArrInit a; 
} 

用Visual C++ 2008 32位與最佳化開啓編譯此編譯環路 -

; Line 12 
    xor eax, eax 
    mov ecx, 1024    ; 00000400H 
    mov edi, edx 
    rep stosd 

這是非常正是memset的可能會編譯反正。但是如果你使用memset,那麼編譯器沒有執行進一步優化的空間,而通過編寫你的意圖,編譯器可以執行進一步的優化,例如注意到每個元素在被使用之前被設置爲別的東西,所以可以對初始化進行優化,如果您使用過memset,則可能無法輕鬆完成初始化。

+0

我的理解當然是默認的初始化程序也會將數組歸零,所以這只是一個例子,但是它的意義在於實現您的需求,在這種情況下,需要將每個數組元素設置爲零,而不是其他一些方法來實現結果除非它是你可以實現其他要求的唯一方法,例如性能 – jcoder 2009-12-29 18:16:25

+0

'這幾乎就是memset可能編譯的內容。'不,memset可能比簡單的'rep stosd'更加複雜和高效' – zhangyoufu 2016-11-07 15:43:53

49

在C++ std::fillstd::fill_n可能是一個更好的選擇,因爲它是通用的,因此可以操作對象以及POD。但是,memset對原始字節序列進行操作,因此不應用於初始化非POD。無論如何,如果類型是POD,std::fill的優化實現可以在內部使用專業化來呼叫memset

+1

我忘了std :: fill,所以對我來說+1。是的,有一個專門用來填充容器的C++函數,請使用它! – jcoder 2009-12-29 18:19:12

+4

什麼是POD的含義? – Jichao 2009-12-29 18:28:55

+6

http://en.wikipedia.org/wiki/Plain_old_data_structures – Reunanen 2009-12-29 18:31:26

9

什麼在C++錯memset大多在C. memsetmemset同樣的事情罷了存儲區域與物理零位模式,而在病例幾乎100%的現實需要,以填補邏輯陣列對應類型的零值。在C語言中,memset只能保證正確地初始化整數類型的內存(並且它的有效性爲所有整數類型,而不僅僅是字符類型,是對C語言規範添加的相對最近的保證)。不能保證將任何浮點值正確設置爲零,但不能保證產生正確的空指針。

當然,上述可能被認爲過於迂腐,因爲在給定平臺上活動的附加標準和慣例可能(並且肯定會)會擴展memset的適用性,但是我仍然會建議遵循奧卡姆姆的剃刀原理這裏:不要依賴任何其他標準和慣例,除非你真的必須這樣做。 C++語言(以及C語言)提供了幾種語言級別的功能,可以讓您安全地使用適當類型的適當零值來初始化聚合對象。其他答案已經提到這些功能。

+1

物理和邏輯零之間的區別是什麼? – Adil 2013-05-21 12:13:59

+0

@Adil物理零是內存中明確的實際「全零」位模式。邏輯零是[潛在的非零]位模式,被語言解釋爲某種類型的零值(在我們的例子中是C或C++)。 – AnT 2018-02-10 14:05:22

0

除了應用於班級時的不良情況,memset也容易出錯。無序地獲取參數或者忘記sizeof部分非常容易。代碼通常會編譯這些錯誤,並悄悄地做錯誤的事情。該錯誤的症狀可能直到很晚纔會顯現,這使得難以追蹤。

memset也有許多普通類型,如指針和浮點問題。一些程序員將所有字節設置爲0,假設指針將爲NULL,且浮點數爲0.0。這不是一個便攜式的假設。

+0

將指針和浮點數設置爲二進制零通常有效,但我不想陷入習慣。儘管如此,IEEE浮點標準變得越來越堅定,並且將全零位解釋爲0.0。 – 2009-12-29 22:59:20

+0

@大衛:是的,它通常有效,但有一天你會在一個沒有的平臺上。 – 2009-12-30 16:29:47

0

沒有真正的理由不使用它,除非少數情況下人們指出沒有人會使用它,但除非你正在填寫保護程序或其他東西,否則使用它並沒有真正的好處。

0

簡短的回答將是使用一個std ::矢量與1024

std::vector<int> a(1024); // Uses the types default constructor, "T()". 

初始尺寸的所有元素的初始值「a」是0時,作爲標準::矢量(size)構造函數(以及vector :: resize)複製所有元素的默認構造函數的值。對於內置類型(又名固有的類型,或莢),保證您的初始值是0:

int x = int(); // x == 0 

這將允許「一」使用最小做文章更改類型,甚至是一類。

大多數將void指針(void *)作爲參數(如memset)的函數都不是類型安全的。忽略對象的類型,以這種方式,刪除所有傾向於依賴的C++樣式語義對象,如構造,銷燬和複製。 memset對一個類進行假設,這違反了抽象(不知道或關心類內部的東西)。雖然這種違規行爲並不總是立即顯而易見,特別是對於內部類型而言,它可能會導致難以定位錯誤,特別是隨着代碼庫的不斷增長和易手。如果memset類型是帶有虛表(虛函數)的類,它也會覆蓋該數據。

1

這是一個古老的線程,但這裏有一個有趣的轉折:

class myclass 
{ 
    virtual void somefunc(); 
}; 

myclass onemyclass; 

memset(&onemyclass,0,sizeof(myclass)); 

工作得很好!

然而,

myclass *myptr; 

myptr=&onemyclass; 

memset(myptr,0,sizeof(myclass)); 

確實設置虛函數(即somefunc(上文))爲NULL。

鑑於memset大大快於將每個成員設置爲0以及大型類中的每個成員,所以我一直在做上面的第一個memset並且從來沒有遇到過問題。

所以真正有趣的問題是它是如何工作的?我想,編譯器實際上開始設置零的BEYOND虛擬表...任何想法?

+0

「它不會崩潰或做任何明顯錯誤,我可以看到」和「它的作品」是非常不一樣的事情。 AFAICT上面的兩個代碼片段都是一樣的,但一旦你開始調用未定義的行爲,所有的投注都關閉。上述任何一種程序很可能只會(看起來)在特定情況下工作,並且在其他情況下會崩潰(例如,在不同的編譯器或操作系統或CPU架構上) – 2014-01-04 05:50:00

相關問題