有時我們希望要將一個結構對象清零。此時我們常用的方法是調用memset。來看一下代碼
#include <cstring>struct pod_struct {#ifdef BIG_POD_STRUCT int a[128];#else int a; int b;#endif};int main(){ pod_struct pod; std::memset(&pod, 0, sizeof(pod));}BIG_POD_STRUCT的定義在後續中會用到。我們知道,memset其實是有開銷的,對於比較小的記憶體塊,使用簡單的賦值可能更快。因為memset需要根據目標和源地址計算對齊屬性,選擇最佳的一種方案進行copy。
pod.a = 0;pod.b = 0;
這裡簡單地賦值,效率其實更高。我們來簡單做一下對比。分別使用memset和賦值的方法,迭代1000, 0000次,計算總時間。
#include <cstring>#include <clock.h>struct pod_struct {#ifdef BIG_POD_STRUCT int a[128];#else int a; int b;#endif};int main(){ using namespace my_util; const int COUNT = 10000000; pod_struct pod; SCOPED_CLOCK_BEGIN() for (int i = 0; i < COUNT; ++i) { std::memset(&pod, 0, sizeof(pod)); } SCOPED_CLOCK_END() SCOPED_CLOCK_BEGIN() for (int i = 0; i < COUNT; ++i) { pod.a = 0; pod.b = 0; } SCOPED_CLOCK_END()}這裡的scoped_clock_*宏只是用來測量時間的,實現使用clock()函數完成的,可以精確到毫秒。測量的結果是
| |
memset |
assignment |
| time(ms) |
109 |
31 |
很顯然,用賦值的方法所用的cpu時間更短。由此我們想到能不能有什麼方法可以根據對象的大小來選擇這兩種方法呢?其實編譯器可以幫我們做這些事情。
使用一個initializer對一個pod對象進行初始化,就可以很簡單地把一個對象清零。有時我們會這麼把數組進行清零。
int a[100] = {0}; 這裡,第一個元素初始化為0,剩餘元素自動被編譯器清零。其實更簡單的方法是,連0都不指定,直接使用一個空的初始化列表。標準規定,
If there are fewer initializers in the list than there are members in the aggregate, then each member not
explicitly initialized shall be value-initialized (8.5).
value-initialized對於內建的類型而言,就是zero-initialized,也就是簡單地清零。所以,我們可以這麼初始化pod_struct
pod_struct pod = {};使用這個結構,編譯器會根據目標對象的大小選擇最佳的初始化方法,即使用memset或者賦值。來看一下編譯器在BIG_POD_STRUCT開啟/關閉情況下生存的彙編代碼就一目瞭然了(VC10)。
; BIG_POD_STRUCT not defined xor eax,eax mov dword ptr [pod],eax mov dword ptr [ebp-8],eax
這裡編譯器把eax的值賦值給了[pod],也就是pod_struct.a,之後再賦值給了pod_struct.b。再看一下BIG_POD_STRUCT開啟後的代碼,
; BIG_POD_STRUCT defined push 200h push 0 lea eax,[pod] push eax call @ILT+440(_memset)
這裡編譯器調用了memset,而由於memset是__cdecl函數,所以參數壓棧從右至左。size=200H,value=0,dest=&pod,分別被壓棧後調用memset。200H正好是pod_struct的大小。
我測試了一下編譯器會將賦值替換為memset實現的閾值,在我的機器上是40個位元組,VC就會使用memset對pod_struct進行清零。