1. 程式人生 > >C++ 之memset() 詳解

C++ 之memset() 詳解

在C/C++語言中,經常需要對記憶體進行操作,裡面涉及很多函式,但是memset函式的使用需格外注意!     函式原型是:void *memset(void *s, int ch, size_t n);
    函式功能是:將s所指向的某一塊記憶體中的前n個位元組的內容全部設定為ch指定的ASCII值, 第一個值為指定的記憶體地址,塊的大小由第三個引數指定,這個函式通常為新申請的記憶體做初始化工作, 其返回值為指向s的指標,它是對較大的結構體或陣列進行清零操作的一種最快方法
    標頭檔案是:<memory.h>或<string.h>

    1. 基本應用場合

    memset函式通常用來對一塊已經分配地址的記憶體進行初始化,並且通常初始化為0或者字元'\0'(實際上是一樣的)。下面是一些常見例子。
/*===注意:這些例子如果同時執行需要在C++編譯環境下===*/  
int i = 0;  
// 例1:對字元陣列進行初始化  
char buf[10];  
memset(buf, '\0', sizeof(char) * 10);   // 0或者'\0'是等價的可檢視ASCII碼,可見下表  
for (i = 0; i < 10; ++i)  
{  
    printf("%c", buf[i]);  
}  
printf("\n");  
  
// 例2:對字元指標所指區域初始化,必須已經分配記憶體  
char* pBuf = (char *)malloc(sizeof(char) * 10);  
  
if (pBuf != NULL)  
{  
    memset(pBuf, 0, sizeof(char) * 10);     // 0或者'\0'是等價的  
    for (i = 0; i < 10; ++i)  
    {  
        printf("%c", pBuf[i]);  
    }  
    printf("\n");  
  
    free(pBuf);  
    pBuf = NULL;  
}  
  
// 例3:對整型陣列進行初始化  
int iBuf[10];  
memset(iBuf, 0, sizeof(int) * 10);  
for (i = 0; i < 10; ++i)  
{  
    printf("%d ", iBuf[i]);  
}  
printf("\n");  

    上面的這些例子已經比較清楚地展示了memset函式的使用,當然,很常見的還有對結構體進行這樣的初始化操作,唯一的區別就是sizeof()的物件變成結構體即可,這裡大家可以自己嘗試。

    2. 需要注意的幾點

(1)memset中的第三個引數一定要使用sizeof操作符,因為每個系統下對型別長度的定義可能不一樣。 (2)memset中的第一個引數一定要是一個已知的、已經被分配記憶體的地址,否則會出錯。 (3)大家可能比較疑惑,memset的第一個引數已經有了被初始化空間的首地址,為什麼還要返回一個void*的指標去指向這個地址呢?這種結構在很多函式庫裡面比較常見,比如字串操作函式等,都有類似的現象,這裡之所以還要返回這個指標是為了實現鏈式程式設計,所謂鏈式程式設計,舉個例子大家就明白了。
// 例4:鏈式程式設計  
int i = 0;  
char cBuf [10];  
char cBuf1[10];  
  
// 這裡是關鍵!!!  
memcpy(cBuf1, memset(cBuf, 'a', sizeof(char) * 10), sizeof(char) * 10);  
  
for (i = 0; i < 10; ++i)  
{  
    printf("%c", cBuf[i]);  
}  
printf("\n");  
  
for (i = 0; i < 10; ++i)  
{  
    printf("%c", cBuf1[i]);  
}  
printf("\n");  

    從上面這個例子中就可以看出,在memcpy這個函式中,直接使用了memset的返回值,用其來拷貝cBuf1這個字元陣列,這樣就可以直接連起來寫,看起來十分方便。但是這個例子的應用形式卻很少,只是為了說明這個問題才這樣寫的,具體的鏈式程式設計應用場合大家可以再仔細研究下,但是鏈式程式設計也使得程式碼變得有些不直觀,所以要有所取捨。 (4)最重要的一點。一定要注意,memset是按照位元組對待初始化空間進行初始化的,也就是說,函式裡面的第二個引數的那個初值(一般為0)是按照一個一個位元組往第一個引數所指區域賦值的,所以,對於單位元組資料型別(char)可以初始化為任意支援的值,都沒有問題,但是對於非多位元組資料型別只能初始化為0,而不能初始化成別的初值,因為對所有位元組按任意順序賦值0的結果都是0,而如果初始化為其他的值,就會一個位元組一個位元組的進行賦值,從而出現奇怪的結果。比如說,上面的例3之所以沒有出錯就是因為初始化為0,但是如果初始化為1,那麼因為int一般是4個位元組,那麼相當於將一個int元素初始化成了0000 0001 0000 0001 0000 0001 0000 0001,這樣對於一個int元素肯定不是1,而是一個很大的數,結果出乎意料,所以一定要記住這一點,非常重要!!! 關於此點的詳細說明如下: 剛才函式原型:
void * memset ( void * ptr, int value, size_t num );
為地址ptr開始的num個位元組賦值value,注意:是逐個位元組賦值,ptr開始的num個位元組中的每個位元組都賦值為value。

(1) 若ptr指向char型地址,value可為任意字元值;

(2) 若ptr指向非char型,如int型地址,要想賦值正確,value的值只能是-1或0,因為-1和0轉化成二進位制後每一位都是一樣的,設int型佔4個位元組,則-1=0XFFFFFFFF, 0=0X00000000

例,正確賦值:

int A[2];  
memset(A, -1, sizeof A);  

賦值過程如下圖:


由於int型佔四個位元組,memset的賦值方式是逐個位元組賦值,因此A[0]或A[1]實際上是四個位元組合在一起的值,即0XFFFFFFFF=-1。

例,錯誤賦值:

int A[2];  
memset(A, 1, sizeof A);  
賦值過程如下圖:


A[0]的值實際上是0X01010101=16843009,因此不是我們所期望的哦。 (其中0X指十六進位制,int型別四個位元組,每個位元組八位,一個十六進位制對應四個二進位制) 附表:ASCII碼(摘自百度百科)
  1. void * memset ( void * ptr, int value, size_t num );  
為地址ptr開始的num個位元組賦值value,注意:是逐個位元組賦值,ptr開始的num個位元組中的每個位元組都賦值為value。

(1) 若ptr指向char型地址,value可為任意字元值;

(2) 若ptr指向非char型,如int型地址,要想賦值正確,value的值只能是-1或0,因為-1和0轉化成二進位制後每一位都是一樣的,設int型佔4個位元組,則-1=0XFFFFFFFF, 0=0X00000000。

例,正確賦值:

  1. int A[2];  
  2. memset(A, -1, sizeof A);  

賦值過程如下圖:


由於int型佔四個位元組,memset的賦值方式是逐個位元組賦值,因此A[0]或A[1]實際上是四個位元組合在一起的值,即0XFFFFFFFF=-1。

例,錯誤賦值:

  1. int A[2];  
  2. memset(A, 1, sizeof A);  
賦值過程如下圖:


A[0]的值實際上是0X01010101=16843009,因此不是我們所期望的哦。