C语言如何初始化静态变量

通过一个例子说明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;
}

说明:

  1. 这个例子定义了一个静态数组变量my_functions;
  2. 数组变量的成员类型是结构体FunctionSpec,包含5个域
  3. 数组变量的大小是4,即有4个成员
    第一个成员初始化提供了5个域
    第二个成员初始化提供了4个域
    第三个成员初始化提供了3个域
    第四个成员初始化提供了1个域。

也就是说,程序员为FunctionSpec类型变量的初始化,有些提供了全5个值,而有些只提供了4个值,或者3个值,甚至1个域值;那么剩下没有提供值得域会怎么处理呢。

运行步骤

  1. 编译运行
$ gcc test.c
$ ./a.out
sizeof FunctionSpec=24
sizeof my_functions=96

说明:FunctionSpec共有5个域:

  1. 第一个域是pointer,占用8字节
  2. 第二个域也是pointer,占用8字节
  3. 第三个域是char,占用1字节
  4. 第四个域是char,占用1字节
  5. 第五个域是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 :
  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)
  1. 第一个域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

  1. 第二个域8字节,其值为00000000004004c4,指向函数foo1的地址:
00000000004004c4 :

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. 第三个域1字节:0x0b = 11
  2. 第四个域1字节:0x16 = 22
  3. 第五个域2自己:0x0021 = 33
  4. 最后是4字节的pad: 0x000000

后面的记录数据一样,不一一列举了,只是没有显示的值都为零。

你可能感兴趣的:(C语言如何初始化静态变量)