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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139