通過一個例子說明C語言如何初始化靜態(tài)變量。
給出C語言代碼例子
這個例子在linux gcc x86_64環(huán)境下驗證删掀。
typedef int (* Fun)(void * obj, int argc, int *argv);
struct FunctionSpec {
const char *name; /* 8 byte */
Fun call; /* 8 byte */
unsigned char nargs; /* 1 byte */
unsigned char flags; /* 1 byte*/
unsigned short extra; /* 2 byte */
};
static int foo1(void *obj, int argc, int *argv) { return 0; }
static int foo2(void *obj, int argc, int *argv) { return 0; }
static int foo3(void *obj, int argc, int *argv) { return 0; }
static struct FunctionSpec my_functions[] = {
{"foo1", foo1, 11, 22, 33},
{"foo2", foo2, 44, 55},
{"foo3", foo3, 66},
{0}
};
int main(int argc, char * argv[])
{
printf("sizeof FunctionSpec=%d\n", sizeof(struct FunctionSpec));
printf("sizeof my_functions=%d\n", sizeof(my_functions));
return 0;
}
說明:
- 這個例子定義了一個靜態(tài)數(shù)組變量my_functions翔冀;
- 數(shù)組變量的成員類型是結(jié)構(gòu)體FunctionSpec,包含5個域
- 數(shù)組變量的大小是4披泪,即有4個成員
第一個成員初始化提供了5個域
第二個成員初始化提供了4個域
第三個成員初始化提供了3個域
第四個成員初始化提供了1個域纤子。
也就是說,程序員為FunctionSpec類型變量的初始化款票,有些提供了全5個值控硼,而有些只提供了4個值,或者3個值徽职,甚至1個域值象颖;那么剩下沒有提供值得域會怎么處理呢。
運行步驟
- 編譯運行
$ gcc test.c
$ ./a.out
sizeof FunctionSpec=24
sizeof my_functions=96
說明:FunctionSpec共有5個域:
- 第一個域是pointer姆钉,占用8字節(jié)
- 第二個域也是pointer说订,占用8字節(jié)
- 第三個域是char,占用1字節(jié)
- 第四個域是char潮瓶,占用1字節(jié)
- 第五個域是short陶冷,占用2字節(jié)
一共需要8+8+1+1+2=20字節(jié),但是由于對齊(align)的原因毯辅,會有額外四個字節(jié)的padd對齊空間埂伦,從而實際占用空間為24字節(jié)。
查看my_functions變量的匯編代碼
因為my_functions是靜態(tài)變量思恐,從程序匯編代碼就能夠看出其變量的內(nèi)容沾谜。
.section .rodata
.LC0:
.string "foo1"
.LC1:
.string "foo2"
.data
.align 32
.type my_functions, @object
.size my_functions, 96
my_functions:
.quad .LC0 # 第一個元素: 8 字節(jié)指針指向字符串foo1的地址
.quad foo1 # 8 字節(jié)指針指向函數(shù)foo1的地址
.byte 11 # 1 字節(jié)參數(shù)nargs的值
.byte 22 # 1 字節(jié)參數(shù)flags的值
.value 33 # 1 字節(jié)參數(shù)extra的值
.zero 4 # 4 字節(jié)的padd, 和前面4字節(jié)構(gòu)成一個8字節(jié)的標(biāo)準(zhǔn)長度
.quad .LC1 # 第二個元素: 8 字節(jié)指針指向字符串foo2的地址
.quad foo2 # 8 字節(jié)指針指向函數(shù)foo2的地址
.byte 44 # 1 字節(jié)參數(shù)nargs的值
.byte 55 # 1 字節(jié)參數(shù)flags的值
.zero 6 # 6 字節(jié)的padd, 和前面2字節(jié)構(gòu)成一個8字節(jié)的標(biāo)準(zhǔn)長度, 因為這個元素只給參數(shù)nargs和flags賦了值,剩下參數(shù)是0
.quad .LC2 # 第二個元素: 8 字節(jié)指針指向字符串foo3的地址
.quad foo3 # 8 字節(jié)指針指向函數(shù)foo3的地址
.byte 66 # 1 字節(jié)參數(shù)nargs的值
.zero 7 # 7 字節(jié)的padd, 和前面2字節(jié)構(gòu)成一個8字節(jié)的標(biāo)準(zhǔn)長度, 因為這個元素只給參數(shù)nargs賦了值胀莹,剩下參數(shù)都是0
.quad 0 # 第三個元素
.zero 16 # 所有的參數(shù)都是缺省值0
.section .rodata
我們看到?jīng)]有分配的域編譯器都生成.zero匯編偽代碼基跑。
下面我們再看一下最終生成的可執(zhí)行文件a.out里面相關(guān)的內(nèi)容
$ objdump -D -S a.out
注意因為這個反匯編出來的內(nèi)容,沒法區(qū)分是指令還是數(shù)據(jù)描焰,所有統(tǒng)統(tǒng)轉(zhuǎn)換成了指令媳否,我們只關(guān)注內(nèi)容數(shù)據(jù)即可,不必注意對應(yīng)的指令是什么荆秦,本身他們就不是指令篱竭,是強翻譯的而已。
變量my_functions的全部內(nèi)容如下:
00000000006009c0 <my_functions>:
6009c0: 48 06 rex.W (bad)
6009c2: 40 00 00 add %al,(%rax)
6009c5: 00 00 add %al,(%rax)
6009c7: 00 c4 add %al,%ah
6009c9: 04 40 add $0x40,%al
6009cb: 00 00 add %al,(%rax)
6009cd: 00 00 add %al,(%rax)
6009cf: 00 0b add %cl,(%rbx)
6009d1: 16 (bad)
6009d2: 21 00 and %eax,(%rax)
6009d4: 00 00 add %al,(%rax)
6009d6: 00 00 add %al,(%rax)
6009d8: 4d 06 rex.WRB (bad)
6009da: 40 00 00 add %al,(%rax)
6009dd: 00 00 add %al,(%rax)
6009df: 00 da add %bl,%dl
6009e1: 04 40 add $0x40,%al
6009e3: 00 00 add %al,(%rax)
6009e5: 00 00 add %al,(%rax)
6009e7: 00 2c 37 add %ch,(%rdi,%rsi,1)
6009ea: 00 00 add %al,(%rax)
6009ec: 00 00 add %al,(%rax)
6009ee: 00 00 add %al,(%rax)
6009f0: 52 push %rdx
6009f1: 06 (bad)
6009f2: 40 00 00 add %al,(%rax)
6009f5: 00 00 add %al,(%rax)
6009f7: 00 f0 add %dh,%al
6009f9: 04 40 add $0x40,%al
6009fb: 00 00 add %al,(%rax)
6009fd: 00 00 add %al,(%rax)
6009ff: 00 42 00 add %al,0x0(%rdx)
...
我們知道m(xù)y_functions變量包含5條值:
第一條數(shù)據(jù)24個字節(jié)長度:
6009c0: 48 06 rex.W (bad)
6009c2: 40 00 00 add %al,(%rax)
6009c5: 00 00 add %al,(%rax)
6009c7: 00 c4 add %al,%ah
6009c9: 04 40 add $0x40,%al
6009cb: 00 00 add %al,(%rax)
6009cd: 00 00 add %al,(%rax)
6009cf: 00 0b add %cl,(%rbx)
6009d1: 16 (bad)
6009d2: 21 00 and %eax,(%rax)
6009d4: 00 00 add %al,(%rax)
6009d6: 00 00 add %al,(%rax)
- 第一個域8字節(jié)步绸,其值為0000000000400648掺逼,指向字符串"foo1"的地址:
400648: 66 6f outsw %ds:(%rsi),(%dx)
40064a: 6f outsl %ds:(%rsi),(%dx)
40064b: 31 00 xor %eax,(%rax)
其值正好是"foo1": 0x66-0x6f-0x6f-0x31-0x00
- 第二個域8字節(jié),其值為00000000004004c4瓤介,指向函數(shù)foo1的地址:
00000000004004c4 <foo1>:
static int foo1(void *obj, int argc, int *argv) { return 0; }
4004c4: 55 push %rbp
4004c5: 48 89 e5 mov %rsp,%rbp
4004c8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
4004cc: 89 75 f4 mov %esi,-0xc(%rbp)
4004cf: 48 89 55 e8 mov %rdx,-0x18(%rbp)
4004d3: b8 00 00 00 00 mov $0x0,%eax
4004d8: c9 leaveq
4004d9: c3 retq
- 第三個域1字節(jié):0x0b = 11
- 第四個域1字節(jié):0x16 = 22
- 第五個域2自己:0x0021 = 33
- 最后是4字節(jié)的pad: 0x000000
后面的記錄數(shù)據(jù)一樣坪圾,不一一列舉了晓折,只是沒有顯示的值都為零。