通过一个例子说明C语言如何初始化静态变量。
给出C语言代码例子
这个例子在linux gcc x86_64环境下验证。
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;
}
说明:
- 这个例子定义了一个静态数组变量my_functions;
- 数组变量的成员类型是结构体FunctionSpec,包含5个域
- 数组变量的大小是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字节
- 第二个域也是pointer,占用8字节
- 第三个域是char,占用1字节
- 第四个域是char,占用1字节
- 第五个域是short,占用2字节
一共需要8+8+1+1+2=20字节,但是由于对齐(align)的原因,会有额外四个字节的padd对齐空间,从而实际占用空间为24字节。
查看my_functions变量的汇编代码
因为my_functions是静态变量,从程序汇编代码就能够看出其变量的内容。
.section .rodata
.LC0:
.string "foo1"
.LC1:
.string "foo2"
.data
.align 32
.type my_functions, @object
.size my_functions, 96
my_functions:
.quad .LC0 # 第一个元素: 8 字节指针指向字符串foo1的地址
.quad foo1 # 8 字节指针指向函数foo1的地址
.byte 11 # 1 字节参数nargs的值
.byte 22 # 1 字节参数flags的值
.value 33 # 1 字节参数extra的值
.zero 4 # 4 字节的padd, 和前面4字节构成一个8字节的标准长度
.quad .LC1 # 第二个元素: 8 字节指针指向字符串foo2的地址
.quad foo2 # 8 字节指针指向函数foo2的地址
.byte 44 # 1 字节参数nargs的值
.byte 55 # 1 字节参数flags的值
.zero 6 # 6 字节的padd, 和前面2字节构成一个8字节的标准长度, 因为这个元素只给参数nargs和flags赋了值,剩下参数是0
.quad .LC2 # 第二个元素: 8 字节指针指向字符串foo3的地址
.quad foo3 # 8 字节指针指向函数foo3的地址
.byte 66 # 1 字节参数nargs的值
.zero 7 # 7 字节的padd, 和前面2字节构成一个8字节的标准长度, 因为这个元素只给参数nargs赋了值,剩下参数都是0
.quad 0 # 第三个元素
.zero 16 # 所有的参数都是缺省值0
.section .rodata
我们看到没有分配的域编译器都生成.zero汇编伪代码。
下面我们再看一下最终生成的可执行文件a.out里面相关的内容
$ objdump -D -S a.out
注意因为这个反汇编出来的内容,没法区分是指令还是数据,所有统统转换成了指令,我们只关注内容数据即可,不必注意对应的指令是什么,本身他们就不是指令,是强翻译的而已。
变量my_functions的全部内容如下:
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)
...
我们知道my_functions变量包含5条值:
第一条数据24个字节长度:
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字节,其值为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字节,其值为00000000004004c4,指向函数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字节:0x0b = 11
- 第四个域1字节:0x16 = 22
- 第五个域2自己:0x0021 = 33
- 最后是4字节的pad: 0x000000
后面的记录数据一样,不一一列举了,只是没有显示的值都为零。