2015年11月16日 星期一

C 語言 - 初始化非零的 struct 陣列


在 Linux Kernel 的程式碼中常常可以看到有趣的語法,像是底下初始化中斷的方式:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {                
        [0 ... NR_IRQS-1] = { 
                .handle_irq     = handle_bad_irq,
                .depth          = 1,
                .lock           = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
        }   

};

先不去查語法大概可以猜出來這是初始化大小是 NR_IRQS 的struct 陣列,初始化的語法很有趣,方法是 [0 ... NR_IRQS-1] = {}  。


這是 C99 標準的語法 "Designated Initializer",看一個簡化的版本大概就可以猜出來要如何初始化:

int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };

這把前十個初始化為 0,  Index從10到99設為2,  Index 100設為 3。




雖然C99是 1999年的標準,但應該很少人想到有這樣的語法,雖然 For Loop 也可以辦到同樣的事,但能少打幾行code還是比較開心,但也讓我好奇到底如何做到的。就用下面的程式來看看GCC實際的做法

( 程式可以直接下載)

  1 #include <stdio.h>
  2 
  3 int widths_global[] = { [0 ... 9] = 2, [10 ... 99] = 3, [100] = 4 };
  4 
  5 int main(int argc, char* argv[])
  6 {
  7     int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
  8     int size = sizeof(widths) / sizeof(int);
  9     int i;
 10 
 11     for( i = 0; i < size; i++)
 12         printf("widths[%d] = %d\n", i, widths[i]);                                          
 13     return 0;

 14 }

這裡用了兩個 Integer Arrays,一個是全域變數 "widths_global []",另一個是區域變數 "widths []"。照一般作法,已經初始化全域變數會放在 .data section,沒有初始化的全域變數會放在 .bss section,而區域變數會放在 stack 內,等function結束後就會POP out 區域變數。全域變數 "widths_global" 應該是compiler在編譯時就填好初始值到 .data section。而區域變數 "widths" 到底是用什麼方式初始化?是用for loop嗎?




直接編譯程式然後反組譯看看
(反組譯後的檔案可以直接下載)

$ gcc t_designated_initializers.c -g -O0
參數 -g是加入 debug 資訊
參數 -O0 是不要最佳化

$ objdump -D -s -S a.out  > objdump.txt
參數
-D: 反組譯所有內容
-s : 所有section內的內容
-S: 加入source codes

反組譯 .data section的內容如下:
 143 Contents of section .data:
 144  107f4 00000000 00000000 02000000 02000000  ................
 145  10804 02000000 02000000 02000000 02000000  ................
 146  10814 02000000 02000000 02000000 02000000  ................
 147  10824 03000000 03000000 03000000 03000000  ................
 148  10834 03000000 03000000 03000000 03000000  ................
 149  10844 03000000 03000000 03000000 03000000  ................
 150  10854 03000000 03000000 03000000 03000000  ................
 151  10864 03000000 03000000 03000000 03000000  ................
 152  10874 03000000 03000000 03000000 03000000  ................
 153  10884 03000000 03000000 03000000 03000000  ................
 154  10894 03000000 03000000 03000000 03000000  ................
 155  108a4 03000000 03000000 03000000 03000000  ................
 156  108b4 03000000 03000000 03000000 03000000  ................
 157  108c4 03000000 03000000 03000000 03000000  ................
 158  108d4 03000000 03000000 03000000 03000000  ................
 159  108e4 03000000 03000000 03000000 03000000  ................
 160  108f4 03000000 03000000 03000000 03000000  ................
 161  10904 03000000 03000000 03000000 03000000  ................
 162  10914 03000000 03000000 03000000 03000000  ................
 163  10924 03000000 03000000 03000000 03000000  ................
 164  10934 03000000 03000000 03000000 03000000  ................
 165  10944 03000000 03000000 03000000 03000000  ................
 166  10954 03000000 03000000 03000000 03000000  ................
 167  10964 03000000 03000000 03000000 03000000  ................
 168  10974 03000000 03000000 03000000 03000000  ................
 169  10984 03000000 03000000 04000000           ............
可以看出來 widths_global[] 就放在 0x107fc -- 0x1098c 內,符合當初預期。但是 widths[]呢,這就有點複雜了,需要看使用時的code,所以要看 main的組合語言。





反組譯 main()的內容如下:
 487 int main(int argc, char* argv[])
 488 {
 489     8400:   e92d4800    push    {fp, lr}
 490     8404:   e28db004    add fp, sp, #4
 491     8408:   e24ddf6a    sub sp, sp, #424    ; 0x1a8
 492     840c:   e50b01a8    str r0, [fp, #-424] ; 0x1a8
 493     8410:   e50b11ac    str r1, [fp, #-428] ; 0x1ac
 494     int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
 495     8414:   e59f3088    ldr r3, [pc, #136]  ; 84a4 <main+0xa4>
 496     8418:   e24b1e1a    sub r1, fp, #416    ; 0x1a0
 497     841c:   e1a02003    mov r2, r3
 498     8420:   e3a03f65    mov r3, #404    ; 0x194
 499     8424:   e1a00001    mov r0, r1
 500     8428:   e1a01002    mov r1, r2
 501     842c:   e1a02003    mov r2, r3
 502     8430:   ebffffbb    bl  8324 <memcpy@plt>
 503     int size = sizeof(widths) / sizeof(int);
 504     8434:   e3a03065    mov r3, #101    ; 0x65
 505     8438:   e50b300c    str r3, [fp, #-12]
 506     int i;
 507 
 508     for( i = 0; i < size; i++)
 509     843c:   e3a03000    mov r3, #0
 510     8440:   e50b3008    str r3, [fp, #-8]
 511     8444:   ea00000e    b   8484 <main+0x84>
 512         printf("widths[%d] = %d\n", i, widths[i]);                                           
 513     8448:   e59f2058    ldr r2, [pc, #88]   ; 84a8 <main+0xa8>
 514     844c:   e51b1008    ldr r1, [fp, #-8]
 515     8450:   e59f3054    ldr r3, [pc, #84]   ; 84ac <main+0xac>
 516     8454:   e1a01101    lsl r1, r1, #2
 517     8458:   e24b0004    sub r0, fp, #4
 518     845c:   e0801001    add r1, r0, r1
 519     8460:   e0813003    add r3, r1, r3
 520     8464:   e5933000    ldr r3, [r3]
 521     8468:   e1a00002    mov r0, r2
 522     846c:   e51b1008    ldr r1, [fp, #-8]
 523     8470:   e1a02003    mov r2, r3
 524     8474:   ebffffa7    bl  8318 <printf@plt>
 525 {
 526     int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
 527     int size = sizeof(widths) / sizeof(int);
 528     int i;
 529 
 530     for( i = 0; i < size; i++)
 531     8478:   e51b3008    ldr r3, [fp, #-8]
 532     847c:   e2833001    add r3, r3, #1
 533     8480:   e50b3008    str r3, [fp, #-8]
 534     8484:   e51b2008    ldr r2, [fp, #-8]
 535     8488:   e51b300c    ldr r3, [fp, #-12]
 536     848c:   e1520003    cmp r2, r3
 537     8490:   baffffec    blt 8448 <main+0x48>
 538         printf("widths[%d] = %d\n", i, widths[i]);                                           
 539     return 0;
 540     8494:   e3a03000    mov r3, #0
 541 }
 542     8498:   e1a00003    mov r0, r3
 543     849c:   e24bd004    sub sp, fp, #4
 544     84a0:   e8bd8800    pop {fp, pc}
 545     84a4:   00008534    andeq   r8, r0, r4, lsr r5
 546     84a8:   00008520    andeq   r8, r0, r0, lsr #10
 547     84ac:   fffffe64            ; <UNDEFINED> instruction: 0xfffffe64





恩... 程式碼挺長的,先看前面幾行,Line 491會配置420 bytes的區域變數,不過程式碼內只有 int widths[101],int size,int i 這三個變數,加起來是 (101 + 1 + 1 ) * 4 = 412,剩下的8 bytes是什麼?  從Line 492 ~ 493 可以看出來是用來儲存 r0 還有 r1。
 487 int main(int argc, char* argv[])
 488 {
 489     8400:   e92d4800    push    {fp, lr}
 490     8404:   e28db004    add fp, sp, #4                => fp = sp + 4
 491     8408:   e24ddf6a    sub sp, sp, #424    ; 0x1a8   => 配置 420 bytes的
 492     840c:   e50b01a8    str r0, [fp, #-424] ; 0x1a8
 493     8410:   e50b11ac    str r1, [fp, #-428] ; 0x1ac
 494     int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };


可以整理出目前的 Stack內容如下





接著繼續往下看,line 495是把 0x84a4的內容放到r3內
 495     8414:   e59f3088    ldr r3, [pc, #136]  ; 84a4 <main+0xa4>
 496     8418:   e24b1e1a    sub r1, fp, #416    ; 0x1a0
 497     841c:   e1a02003    mov r2, r3
 498     8420:   e3a03f65    mov r3, #404    ; 0x194
 499     8424:   e1a00001    mov r0, r1
 500     8428:   e1a01002    mov r1, r2
 501     842c:   e1a02003    mov r2, r3
 502     8430:   ebffffbb    bl  8324 <memcpy@plt>



0x84a4的內容是 0x8534,看起來是沒有什麼意義的東西,也許是一個記憶體位置。
 545     84a4:   00008534 





這就要看如何使用他了,r3 藉著會被搬到 r2,然後在被搬到 r1,最後會在 line 502呼叫 memcpy。所以 0x8534會被當成 memcpy的第二個參數。查詢一下 memcpy()的參數。
$ man memcpy
MEMCPY(3)                  Linux Programmer's Manual                 MEMCPY(3)

NAME
       memcpy - copy memory area

SYNOPSIS
       #include <string.h>

       void *memcpy(void *dest, const void *src, size_t n);

DESCRIPTION
       The  memcpy()  function  copies  n bytes from memory area src to memory
       area dest.  The memory areas must not overlap.  Use memmove(3)  if  the
       memory areas do overlap.

這樣可以知道第二個參數是 src,是指向記憶體的指標,這樣就確定 0x8534是記憶體位置了,接著看看內容。
  85 Contents of section .rodata:
  86  851c 01000200 77696474 68735b25 645d203d  ....widths[%d] =
  87  852c 2025640a 00000000 01000000 01000000   %d.............
  88  853c 01000000 01000000 01000000 01000000  ................
  89  854c 01000000 01000000 01000000 01000000  ................
  90  855c 02000000 02000000 02000000 02000000  ................
  91  856c 02000000 02000000 02000000 02000000  ................
  92  857c 02000000 02000000 02000000 02000000  ................
  93  858c 02000000 02000000 02000000 02000000  ................
  94  859c 02000000 02000000 02000000 02000000  ................
  95  85ac 02000000 02000000 02000000 02000000  ................
  96  85bc 02000000 02000000 02000000 02000000  ................
  97  85cc 02000000 02000000 02000000 02000000  ................
  98  85dc 02000000 02000000 02000000 02000000  ................
  99  85ec 02000000 02000000 02000000 02000000  ................
 100  85fc 02000000 02000000 02000000 02000000  ................
 101  860c 02000000 02000000 02000000 02000000  ................
 102  861c 02000000 02000000 02000000 02000000  ................
 103  862c 02000000 02000000 02000000 02000000  ................
 104  863c 02000000 02000000 02000000 02000000  ................
 105  864c 02000000 02000000 02000000 02000000  ................
 106  865c 02000000 02000000 02000000 02000000  ................
 107  866c 02000000 02000000 02000000 02000000  ................
 108  867c 02000000 02000000 02000000 02000000  ................
 109  868c 02000000 02000000 02000000 02000000  ................
 110  869c 02000000 02000000 02000000 02000000  ................
 111  86ac 02000000 02000000 02000000 02000000  ................
 112  86bc 02000000 02000000 03000000           ............

看起來就是 widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 }; 的內容。接著看  memcpy()的另外兩個參數 r0 = fp - d'416 = (old sp + 4) - d'416 = old sp - 412。雖然 412 / 4 = 103,看起來有點奇怪,但是前8 bytes其實是另外兩個區域變數 int i 還有 int size,很有可能是這兩個區域變數。

另外 r1是 404 , 也就是 101 * 4 = 404,所以可以很確認 widths[ ] 是透過 memcpy() 去初始化,而非用 for loop。

可以整理出目前的Stack





Stack裡面還有兩個記憶體不知道存什麼,所以要繼續看下去
 503     int size = sizeof(widths) / sizeof(int);
 504     8434:   e3a03065    mov r3, #101    ; 0x65
 505     8438:   e50b300c    str r3, [fp, #-12]   => (old sp + 4) - 12 = old sp - 8
 506     int i;
 507 
 508     for( i = 0; i < size; i++)
 509     843c:   e3a03000    mov r3, #0
 510     8440:   e50b3008    str r3, [fp, #-8] => (old sp + 4) - 8 = old sp - 4
 511     8444:   ea00000e    b   8484 <main+0x84>

Line 504, 505可以看出來 old sp - 8 存放值 101,這是 int size = sizeof(widths) / size(int) = 404/ 4 = 0。
Line 509, 510可以看出來 old sp -4 存放值 0,這是 int i = 0。

所以 Stack可以整理出來如下:






結論:
全域變數widths_global[] 會放到 .data section,而且是在編譯階段就填好值,不需在執行時另外花費計算量。

區域變數widths[],除了會耗費 stack size外,還會使用到 .rodata section去儲存初始值。也就是瞬間會用到兩倍widths[]大小的記憶體。