【C/C++】memset方法的誤區

李春港發表於2021-04-14

一、前言

memset 作為對記憶體初始化的函式,還是有不少坑和誤區的,今天就來對這個函式作一個總結。避免後期使用不當踩入坑。

二、函式作用

最簡單的呼叫就是將一個陣列清零,程式碼如下:

const int maxn = 1024;
int a[maxn];
memset(a, 0, sizeof(a)); // 結果:a[0]=a[1]=a[...]=0;
  • 這裡 sizeof(a) = maxn * 4 = 4096;
  • 表示的是將 陣列首地址 a 開始往後的 4096 個位元組,都設定為 0;

三、效率對比

直接呼叫 memset 介面清零 和 呼叫迴圈進行清零,進行一個測試後如下:

對長度為 10000000 的陣列,執行100次呼叫;
模式 memset for
debug 375ms 2156ms
release 343ms 329ms
  • 因為 release 版本會做各種優化,編譯器發現重複執行無效邏輯就會跳過,所以不太好造資料測試,研究時間效率的時候還是參考 debug 版本(當然,軟體釋出的時候肯定用的是 release 版本)。
  • memset 無論從時間效率,還是程式碼整潔來看都是由於 for 迴圈的,當然也帶來了一些容易引起誤解的地方。

四、誤區總結

1、按位元組設定

  • memset 實現原理是根據位元組來設定的,比如對於位元組陣列char a[100],將所有位元組都設定為5,就可以呼叫:
 memset(a, 5, sizeof(a));
  • 但是,對於int b[100],也採用這種方法,就會導致錯誤:
memset(b, 5, sizeof(b));
  • 得到 b 陣列中元素的值為 84215045;
  • 為什麼呢?
  • 我們把這個陣列轉換成二進位制,得到:
  • ( 00000101 00000101 00000101 00000101 ) 2 (00000101 \ 0000 0101 \ 0000 0101 \ 0000 0101)_2 (00000101  00000101  00000101  00000101)2
  • 因為 i n t int int 佔據了 4 4 4 個位元組,把每個位元組都設定成了5,所以最後轉成十進位制就變成了 84215045;
  • 同理,當型別是 short(二位元組整數),或者 long long(八位元組整數)都會有類似問題,總結表格如下:
memset值 char short int long long
0 0 0 0 0
-1 -1 -1 -1 -1
5 5 1285 84215045 361700864190383365
  • 表格中,只有0 和 -1是正常的,因為 0 的二進位制表示中,所有位都為0;-1 的二進位制表示中,所有位都為 1;
  • 特別的,當需要設定的數,對應型別的每個位元組都是同一個數的時候,也可以採用 memset,比如:int 型別的 252645135(十六進位制表示為:0x0f0f0f0f);

2、設定的值只有最低位元組有效

memset(a, 0x05ffffff, sizeof(a));
memset(a, 0xffffff05, sizeof(a));
memset(a, 0xffffff08, sizeof(a));
memset(a, 0x12345678, sizeof(a));
  • 設定值的時候,只會採用最低的位元組作為賦值用,通俗的講,就是以上四句話呼叫,等價於:
memset(a, 0xff, sizeof(a));
memset(a, 0x05, sizeof(a));
memset(a, 0x08, sizeof(a));
memset(a, 0x78, sizeof(a));

3、堆記憶體不可直接 sizeof 取首地址

在堆上申請了一個陣列空間,並且想要給它初始化,呼叫如下:

const int maxn = 1024;
int *p = new [maxn];
memset(p, 0, sizeof(p));
  • 這裡進入了另一個誤區,因為 p p p 在這裡雖然是陣列首地址,但是它扮演的角色更多的,其實是個指標,所以在進行 sizeof 運算子操作的時候,取得的值並不是 4096,而是指標的大小;
  • 32位機子上,指標大小為4,;64位機子上,指標大小為 8;
  • 正確做法是:
const int maxn = 1024;
int *p = new [maxn];
memset(p, 0, maxn * sizeof(int));

4、傳引數組不可直接 sizeof 取首地址

  • 對傳參為陣列的資料進行 memset,呼叫如下:
void fun(int a[maxn])
{    
    memset(a, 0, sizeof(a));
}
  • 這裡呼叫同樣是錯誤的,因為當陣列作為傳參的時候,這裡的 a 已經退化為指標,所以同樣不能用 sizeof 陣列首地址來取大小;
  • 正確做法是:
void fun(int a[maxn]) 
{    
    memset(a, 0, maxn * sizeof(int));
}
  • 當然,當傳參是結構體指標的時候也是如此;

參考於:CSDN- 英雄哪裡出來https://blog.csdn.net/WhereIsHeroFrom/article/details/111660632

相關文章