1. 程式人生 > >C語言 陣列初始化的三種常用方法({0}, memset, for迴圈賦值)以及原理

C語言 陣列初始化的三種常用方法({0}, memset, for迴圈賦值)以及原理

C語言 陣列初始化的三種常用方法({0}, memset, for迴圈賦值)以及原理

C語言中,陣列初始化的方式主要有三種:

1、宣告時,使用 {0} 初始化;

2、使用memset;

3、用for迴圈賦值。

 

那麼,這三種方法的原理以及效率如何呢? 請看下面的測試程式碼:

 

[cpp] view plain copy

 

  1. #define ARRAY_SIZE_MAX  (1*1024*1024)  
  2.   
  3. void function1()  
  4. {  
  5.     char array[ARRAY_SIZE_MAX] = {0};  //宣告時使用{0}初始化為全0  
  6. }  
  7.   
  8. void function2()  
  9. {  
  10.     char array[ARRAY_SIZE_MAX];  
  11.     memset(array, 0, ARRAY_SIZE_MAX);  //使用memset方法  
  12. }  
  13.   
  14. void function3()  
  15. {  
  16.     int i = 0;  
  17.     char array[ARRAY_SIZE_MAX];  
  18.     for (i = 0; i < ARRAY_SIZE_MAX; i++)  //for迴圈賦值  
  19.     {  
  20.         array[i] = 0;  
  21.     }  
  22. }  

 

 

效率:

分別執行上面三種方法,統計下平均時間可以得出:  for迴圈浪費的時間最多,{0} 與memset 耗時差不多。

 

原理:

1、for迴圈,就是迴圈賦值,不解釋了

2、memset,很容易找到memset內部實現程式碼,這裡也不解釋了

3、{0} 內部是怎麼實現的呢?

將上述程式碼編譯成彙編格式如下:

function1如下:

[cpp] view plain copy

 

  1. pushl   %ebp  
  2. movl    %esp, %ebp  
  3. subl    $1048600, %esp  
  4. leal    -1048584(%ebp), %eax  
  5. movl    $1048576, %edx  
  6. movl    %edx, 8(%esp)  
  7. movl    $0, 4(%esp)  
  8. movl    %eax, (%esp)  
  9. call    memset  
  10. leave  
  11. ret  


function2如下:

 

 

 

[cpp] view plain copy

 

  1. pushl   %ebp  
  2. movl    %esp, %ebp  
  3. subl    $1048600, %esp  
  4. movl    $1048576, 8(%esp)  
  5. movl    $0, 4(%esp)  
  6. leal    -1048584(%ebp), %eax  
  7. movl    %eax, (%esp)  
  8. call    memset  
  9. leave  
  10. ret  


通過彙編程式碼可以看出,{0}初始化方式,呼叫了memset函式!

 

對三種方法的選取:

1、for 最浪費時間,不建議(其實memset內部也是用迴圈實現的,只不過memset經過了嚴格優化,所以效能更高);

2、{0} 可能有移植性問題,雖然絕大多數編譯器看到{0} 都是將陣列全部初始化為0, 但是不保證所有編譯器都是這樣實現的;

3、綜合1、2, 推薦使用memset方法。

 

附錄:對於{0}初始化的測試

 

這是很基礎的東西,但基礎的重要性不言而喻,我敢肯定這個知識點我肯定曾經瞭解過,但現在,我不敢確定,由此可見紀錄的重要性,這世界沒有什麼捷徑,找對方向,然後不停重複.所以從今天開始,我會比較詳細的紀錄這些比較小的知識點,其實還是有不少有意思的地方的.


    寫這篇文章的起因在於<<COM技術內幕>>第七章新東西太多,看的我目不暇接,所以在網上找了些例子看,其中就有一個例子中出現了這樣的語句: 

...
wchar_t wname[128]={0};
char cname[256]={0};
...

我感興趣的是:
1.這種賦值的結果.
2.這種形式是否符合標準編碼規則?

我找到了如下資料,可能有助於對這個知識點的掌握.

/*
初始化值的個數可少於陣列元素個數.當初始化值的個數少於陣列元素個數時,前面的按序初始化相應值, 後面的初始化為0(全域性或靜態陣列)或為不確定值(區域性陣列).
*/

 

我相信上面的資料是C和C++語言的標準規範,但實際編譯器處理時,可能會和規範有所不同.因為編譯器原則上要遵從語言規範,但對於區域性陣列的不確定值到底是多少,怎麼處理,編譯器就可以靈活處理.我測試了三種編譯器,其實編譯器賦予的值是固定的,都是0.

在這篇blog中 http://hi.baidu.com/widebright/blog/item/a024bc09631402256b60fbd0.html 談論了相同的話題,現對其摘錄如下: 

/*
一直以為 int a[256]={0};是把a的所有元素初始化為0,int a[256]={1};是把a所有的元素初始化為1.
除錯的時檢視記憶體發現不是那麼一回事,翻了一下《The C++ Programming Language》總算有定論。PDF的竟然不然複製,就把它這章翻譯了,如下

5.2.1   陣列初始化 
陣列可以用一個列值來初始化,例如
         int v1[] ={1,2,3,4};
         char v2[]={'a','b','c',0};
當陣列定義時沒有指定大小,當初始化採用列表初始化了,那麼陣列的大小由初始化時列表元素個數決定。所以v1和v2分別為 int[4] 和char[4]型別。如果明確指定了陣列大小,當在初始化時指定的元素個數超過這個大小就會產生錯誤。例如:
         char   v3[2] ={'a','b',0};   //錯誤:太多的初始化值了
         char   v3[3] ={'a','b',0};   //正確

如果初始化時指定的的元素個數比陣列大小少,剩下的元素都回被初始化為   0。例如
         int   v5[8]={1,2,3,4};
等價於
          int   v5[8]={1,2,3,4,0,0,0,0};

注意沒有如下形式的陣列賦值:
         void f()
         {
             v4={'c','d',0};   //錯誤:不是陣列賦值
         }
如果你想這樣的複製的話,請使用 vector(16章第三節) 或者 valarray(22章第四節)。
        字元陣列可以方便地採用字串直接初始化(參考第五章 2.2小節)
         譯註: 就是 這樣啦   char   alpha []="abcdefghijklmn";

*/

 

下面來看一個例子:

#include <iostream.h>

int array1[5]={1,2,3};
static int array2[5]={1};


void main()
{
    int arr1[5]={2};
    static int arr2[5]={1,2};
    
    int n;
    cout <<"global: ";
    for(n=0; n<5; n++)
        cout <<" " <<array1[n];
    
    cout <<" global static: ";
    for(n=0; n<5; n++)
        cout <<" " <<array2[n];
    
    cout <<" local: ";
    for(n=0; n<5; n++)
        cout <<" " <<arr1[n];
    
    cout <<" local static: ";
    for(n=0; n<5; n++)
        cout <<" " <<arr2[n];
    cout <<endl;
}

在這個例子中,全域性和靜態陣列都按語言規範要求被初始化為0,但是區域性陣列並沒有向前面所說的為不確定值,下面是用gcc,VC6.0,tuborC++分別編譯的結果(注意gcc用g++編譯c++檔案,gcc不會連結庫的):

/*
GCC 可同時用來編譯 C 程式和 C++ 程式。一般來說,C 編譯器通過原始檔的字尾名來判斷是 C 程式還是 C++ 程式。在 Linux 中,C 原始檔的字尾名為 .c,而 C++ 原始檔的字尾名為 .C 或 .cpp。
    但是,gcc 命令只能編譯 C++ 原始檔,而不能自動和 C++ 程式使用的庫連線。因此,通常使用 g++ 命令來完成 C++ 程式的編譯和連線,該程式會自動呼叫 gcc 實現編譯。
*/

 

GCC:

 


 

VC6.0:


 

TurboC++

 


 

    這說明了對區域性陣列沒有初始化的元素的值,這幾種編譯器都將其設定為0.但是,如果如果不對陣列進行初始化,即在定義的同時沒有用列表初始化,那麼區域性陣列的值就取決於編譯器而對程式設計師來說就是不可預料的了.有時間可以測試一下各個編譯器,不過在vc中是0xcc.所以對區域性陣列的初始化要特別小心.但是全域性的陣列和靜態陣列還是會被正確的賦於0值的.

    最後要重申下對變數初始化的重要性, http://blog.vckbase.com/smileonce/archive/2005/06/18/6777.html  這裡列舉了沒有初始化造成的事故.

    此外,這個blog地址值得收藏,在http://blog.vckbase.com/ 排行榜的bl