本文只记录学习过程中整理后的知识结构,不涉及具体细节,具体细节参考man手册和相关书籍,我再怎么说肯定都不如它们说的正确。
内容很浅,如果需要详细细节的可以点叉叉了,对此表示抱歉。
而且本来想写的详细一些,但是写着写着就跑题了,而且因为修改了多次,结果感觉中间错误很多
编译过程很复杂,而且不同的编程语言的编译过程也有所不同。
但是万变不离其踪,该有的过程还是得有,这个过程就是:
(Source Code)-->Scanner-->(Tokens)-->Parser-->(Syntax Tree)-->Semantic Analyzer-->(Commented Syntax Tree)-->Source Code Optimizer-->(Intermediate Representation)-->Code Generator-->(Target Code)-->Code Optimizer-->(Final Target Code)这个过程很长,大致的过程就是:
扫描(词法分析)--》语法分析--》语义分析--》源代码优化--》源代码优化--》代码生成--》目标代码优化
源代码被输入到扫描器(Scanner)将代码分割成一系列的记号(Token)。
例如:lex就是一个词法扫描器。
在1、1中产生的标记,被送入语法分析器,产生语法树(Syntax Tree)
例如:yacc就是一个语法分析器
这部分工作由语义分析器完成,但是它能分析的仅仅是静态语义。
这部分由源码优化器完成,生成相应的中间代码。
常见中间代码有:三地址码,P -码等
1、1~1、4中的内容由被归纳成编译器前端,主要负责产生机器无关的中间代码。
这部分由代码生成器和目标代码优化器完成
1、5中的内容又被划分到编译器后端。主要将中间码变成机器码。
上面的只是一些概念性的东西,其实也没什么大用。
编译过程其实很复杂,不仅和源代码使用的语言类型有关,还和具体的CPU体系相关,所以就以C语言为例,使用GCC编译,CPU是X86架构,下面分两个部分介绍:
(1)从C源码到目标文件
(2)分析目标文件
使用工具:
gcc、objdump,readelf。
功能:
使用指令1编译代码1,得到test.i,内容如下:
# 1 "test.c" # 1 "<command-line>" # 1 "test.c" int main() { int a = 100; returun; }
与test.c比较,容易发现
注意:预编译不检查任何错误,测试代码中,return语句拼写错误,并且不带返回值,但是可以执行成功。
功能
生成汇编源代码
使用指令2编译代码1,得到test.s,其内容如下:
.file "test.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $16, %esp movl $100, -4(%ebp) movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (GNU) 4.7.1 20120721 (prerelease)" .section .note.GNU-stack,"",@progbits
这个是X86下的汇编原代码。以后如果开始看链接过程,就需要对这个语法有一定了解。
汇编我也学的不怎么样。感觉比较重要的就是:
(1)弄明白ebp和esp寄存器作用,因为后面涉及到栈的操作,尤其是函数调用时候的栈操作。
(2)弄明白eax,ebx……几个寄存器的作用,尤其是eax寄存器,系统调用以及函数返回值都需要借助它保存一些临时变量。
(3)当然还有几个在代码中没有体现的,段寄存器和控制寄存器,内存寻址都依靠这些寄存器。
其他的基本看名字都能猜明白是什么意思。应该压力不大了。
功能:
将汇编源代码,编译成可执行指令
使用指令3编译代码1,得到test.o文件
使用指令4,查看得到的test.o文件,显示如下:
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 01 00 03 00 01 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020 fc 00 00 00 00 00 00 00 34 00 00 00 00 00 28 00 |........4.....(.| 00000030 0b 00 08 00 55 89 e5 83 ec 10 c7 45 fc 64 00 00 |....U......E.d..| 00000040 00 b8 00 00 00 00 c9 c3 00 47 43 43 3a 20 28 47 |.........GCC: (G| 00000050 4e 55 29 20 34 2e 37 2e 31 20 32 30 31 32 30 37 |NU) 4.7.1 201207| 00000060 32 31 20 28 70 72 65 72 65 6c 65 61 73 65 29 00 |21 (prerelease).| 00000070 14 00 00 00 00 00 00 00 01 7a 52 00 01 7c 08 01 |.........zR..|..| 00000080 1b 0c 04 04 88 01 00 00 1c 00 00 00 1c 00 00 00 |................| 00000090 00 00 00 00 14 00 00 00 00 41 0e 08 85 02 42 0d |.........A....B.| 000000a0 05 50 c5 0c 04 04 00 00 00 2e 73 79 6d 74 61 62 |.P........symtab| 000000b0 00 2e 73 74 72 74 61 62 00 2e 73 68 73 74 72 74 |..strtab..shstrt| 000000c0 61 62 00 2e 74 65 78 74 00 2e 64 61 74 61 00 2e |ab..text..data..| 000000d0 62 73 73 00 2e 63 6f 6d 6d 65 6e 74 00 2e 6e 6f |bss..comment..no| 000000e0 74 65 2e 47 4e 55 2d 73 74 61 63 6b 00 2e 72 65 |te.GNU-stack..re| 000000f0 6c 2e 65 68 5f 66 72 61 6d 65 00 00 00 00 00 00 |l.eh_frame......| 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 下文忽略 ………………………………内容很乱,后面介绍使用objdump和readelf来分析这个文件。这里仅仅是感受一下。
这部分就要涉及链接器了,而且需要的基础知识还特别多。暂时先不介绍。
2.1~2.4中的内容其实没什么好分析的,首先源码文件都是语法的堆积,分析起来顶多分析下语法规则。但是生成的目标文件我们毕竟没办法用编辑器打开,就好比2.3中打开的就是一堆乱码。
所以好好学习如何分析.o文件才是重点,而且弄明白了.o文件,后面讲述链接的时候才能看明白说的是什么。
先从一下3点说明:
(1)ELF文件有那些类型?
(2)每种类型的结构是什么样的?
(3)结构中每个模块大概有什么内容,我要怎么获取它的信息?
使用file命令查看文件类型:
使用指令5,查看之前编译得到的一些文件,得到如下信息:
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x00245abc5b4fa8dba465e6f58365fb10e1eaadc2, not stripped test.c: C source, ASCII text test.i: C source, ASCII text test.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped test.s: ASCII text
ELF文件类型有4种,这里见到其中的2中,就是可执行文件test和可重定位文件test.o。另外两种是共享目标文件和核心存储文件。
ELF文件不止一种类型,但是它们的基本结构是一致的:
ELF Header Sections Sections Header Table Symbol TablesELF Header:存储了这个ELF文件最基本的信息,主要是标识了ELF文件类型,版本,段表偏移等基本信息。
Sections:中分为很多段,其中最基本的段就是代码段(.text),数据段(.data),只读数据段(.rodata),bss段。不同的ELF类型文件还有各种不同的段,有的段也很重要,遇到再说。
Sections Header Table:Sections内容很多,需要一个表来管理,这个部分内容就是用于管理这些段的。
Symbol Table:符号表,这部分内容也很多,在链接的时候要将不同的目标文件链接起来就是依靠符号表。
分析目标文件的时候,经常得到的都是一堆乱七八糟的的东西,上网找太麻烦了,其实身边就有最好的说明文档,那就是man。使用命令:
$man elf手册中很详细的介绍了ELF文件的内容。看不懂的时候参考它是最好的办法。
从个人的感觉,ELF中最开始需要掌握的就是其中的3张地图,就好像我们来到了一个陌生的城市,最开始需要的其实就是这个城市的地图,而后我们才会有心思去到处观光。
header部分记录的这个ELF文件的基本信息,以及下文的规划。
这张地图嘛,感觉就像说明了这个城市有多大,是一座什么定位的城市,最后还给出了1个很重要的提示“如果想知道这座城市的交通分布,就往XX方向走多少就可以看到Sections Header Table的内容,那里有这座城市的交通分布”。
使用指令7,可以查看test.o文件的header中的内容,得到内容如下:
ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 252 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 11 Section header string table index: 8看着头皮很麻,那就使用man手册来看看吧,
首先,header的信息是用ElfN_Ehdr进行描述的,结构体定义如下:
The ELF header is described by the type Elf32_Ehdr or Elf64_Ehdr: #define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; ElfN_Addr e_entry; ElfN_Off e_phoff; ElfN_Off e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; } ElfN_Ehdr;
两者之间的信息是一一对应的:
header中的魔术信息就是对应结构体中的e_ident。之后依次类推。
之后的内容嘛。。。对着man手册慢慢看吧。
下面是一些觉得比较重要的内容:
(1)注意下魔数中各个字段的概念,这样我们就懂得为什么经常会在shell文件的开头写 “#!/bin/sh”了。
(2)其它字段扫描看一下就好了。
注意Start of section headers字段,这个标识了Sections Header Table的在elf文件中的文件偏移量。
使用指令3编译代码3,得到目标文件test.o
这张就是这个城市的交通地图了,它告诉了我们这座城市的规划布局,以及每个段它在文件中的位置信息。甚至我们可以看出每个段它大概是做什么用的。
使用指令11分析代码3,得到如下信息
There are 13 section headers, starting at offset 0x130: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00000000 000034 00002d 00 AX 0 0 4 [ 2] .rel.text REL 00000000 000430 000018 08 11 1 4 [ 3] .data PROGBITS 00000000 000064 000008 00 WA 0 0 4 [ 4] .bss NOBITS 00000000 00006c 000000 00 WA 0 0 4 [ 5] .rodata PROGBITS 00000000 00006c 000004 00 A 0 0 1 [ 6] .comment PROGBITS 00000000 000070 000028 01 MS 0 0 1 [ 7] .note.GNU-stack PROGBITS 00000000 000098 000000 00 0 0 1 [ 8] .eh_frame PROGBITS 00000000 000098 000038 00 A 0 0 4 [ 9] .rel.eh_frame REL 00000000 000448 000008 08 11 8 4 [10] .shstrtab STRTAB 00000000 0000d0 00005f 00 0 0 1 [11] .symtab SYMTAB 00000000 000338 0000d0 10 12 10 4 [12] .strtab STRTAB 00000000 000408 000026 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)这个部分显示了,ELF中段表部分的内容
描述这部分内容的结构体如下,和上述header中的一样,也是一一对应的关系。
The section header has the following structure: typedef struct { uint32_t sh_name; uint32_t sh_type; uint32_t sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; uint32_t sh_size; uint32_t sh_link; uint32_t sh_info; uint32_t sh_addralign; uint32_t sh_entsize; } Elf32_Shdr;
当然这部分内容还是参考man手册来看了。就不介绍了
下面是一些觉得比较重要的内容:(1)注意其中类型字段和标志字段,做到心中有数
当然除了上述方法还可以使用下面方法查看这个程序的各个段信息。
使用指令3,得到目标文件test.o文件
使用指令6,查看已经编译得到的test.o文件中各个段的基本信息,输出信息如下:
test.o: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000014 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000000 00000000 00000000 00000048 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 00000048 2**2 ALLOC 3 .comment 00000028 00000000 00000000 00000048 2**0 CONTENTS, READONLY 4 .note.GNU-stack 00000000 00000000 00000000 00000070 2**0 CONTENTS, READONLY 5 .eh_frame 00000038 00000000 00000000 00000070 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
这张表描述的内容比上一张表更加简单,但是它很好的描述了这些字段在以后的可执行文件中所在的虚拟文件的位置(需要进行链接后才能看出来)。在链接过程中反而这张表更重要些。
它就是以后程序执行过程中的地图了,当然现在用不上了。
这张地图也很强大,它描述了这座城市中住着那些人。但是这张表仅仅只是表示这个城市有那些人,不描述这些人住在什么地方,而且更重要的是,在没有链接之前,目标文件是不知道这些符号应该在哪个位置的,所以其实链接过程就是安排这些人入住的一个过程。
使用指令3编译代码3,得到目标文件test.o
使用指令12查看test.o
Symbol table '.symtab' contains 13 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS test.c 2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 SECTION LOCAL DEFAULT 4 5: 00000000 0 SECTION LOCAL DEFAULT 5 6: 00000004 4 OBJECT LOCAL DEFAULT 3 b.1359 7: 00000000 0 SECTION LOCAL DEFAULT 7 8: 00000000 0 SECTION LOCAL DEFAULT 8 9: 00000000 0 SECTION LOCAL DEFAULT 6 10: 00000000 4 OBJECT GLOBAL DEFAULT 3 gloabl_var 11: 00000000 45 FUNC GLOBAL DEFAULT 1 main 12: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
如之前介绍的,这个内容使用结构体表示的:
typedef struct { uint32_t st_name; Elf32_Addr st_value; uint32_t st_size; unsigned char st_info; unsigned char st_other; uint16_t st_shndx; } Elf32_Sym;内容也是一对一,参考man手册就可以看到详细信息了。
当然也可以使用objdump命令查看,
使用指令13查看test.o就可以查看文件中的符号表,内容如下:
test.o: file format elf32-i386 SYMBOL TABLE: 00000000 l df *ABS* 00000000 test.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .rodata 00000000 .rodata 00000004 l O .data 00000004 b.1359 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .eh_frame 00000000 .eh_frame 00000000 l d .comment 00000000 .comment 00000000 g O .data 00000004 gloabl_var 00000000 g F .text 0000002d main 00000000 *UND* 00000000 printf这个可以通过查看objdump的man手册查看相关信息。
使用指令8查看test.o文件的.text段反汇编的信息,得到内容如下:
Disassembly of section .text: 00000000 <main>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 10 sub $0x10,%esp 6: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%ebp) d: b8 00 00 00 00 mov $0x0,%eax 12: c9 leave 13: c3 ret
可以计算,text段长度为0x14,和4.1中的长度大小一致。
以下内容为之前得到的test.s文件的内容
.file "test.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $16, %esp movl $100, -4(%ebp) movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (GNU) 4.7.1 20120721 (prerelease)" .section .note.GNU-stack,"",@progbits对比后发现,其实就是把汇编指令翻译成了二进制码。
为了测试,我们在代码1的基础上,添加一行全局变量的申明,并且进行初始化,得到代码2
使用指令3编译代码2,得到新的test.o文件
使用指令6,查看新的test.o各个段的基本信息。得到信息如下:
test.o: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000014 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000004 00000000 00000000 00000048 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 0000004c 2**2 ALLOC 3 .comment 00000028 00000000 00000000 0000004c 2**0 CONTENTS, READONLY 4 .note.GNU-stack 00000000 00000000 00000000 00000074 2**0 CONTENTS, READONLY 5 .eh_frame 00000038 00000000 00000000 00000074 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
重复指令3和指令6,就可以发现.data字段增大为0x8了
然后使用指令9(这里用的测试代码是代码2),查看相关数据段的信息。找到以下两行信息:
Contents of section .data: 0000 64000000 d...我们在代码2中声明了一个全局变量,并初始化为100,100表示成2进制为 1100100,正好是0x64(1100100)的大小
注:这里涉及了计算机中大端存储和小端存储的问题。所以看的时候不要看错了。
在代码3的基础上,加入未初始化的全局变量和局部静态变量申明。然后使用指令3和指令6。得到如下信息:
test.o: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000014 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000008 00000000 00000000 00000048 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000004 00000000 00000000 00000050 2**2 ALLOC 3 .comment 00000028 00000000 00000000 00000050 2**0 CONTENTS, READONLY 4 .note.GNU-stack 00000000 00000000 00000000 00000078 2**0 CONTENTS, READONLY 5 .eh_frame 00000038 00000000 00000000 00000078 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA这里会有一个很奇怪的现象:就是明明申明了2个变量,大小应该是8字节才对,但是为什么.bss段大小只有4字节。
使用指令10,查看test.o文件,得到如下信息(截取部分):
……………………………… SYMBOL TABLE: 00000000 l df *ABS* 00000000 test.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l O .bss 00000004 c.1361 00000004 l O .data 00000004 b.1360 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .eh_frame 00000000 .eh_frame 00000000 l d .comment 00000000 .comment 00000000 g O .data 00000004 gloabl_var 00000004 O *COM* 00000004 global_var2 00000000 g F .text 00000014 main ………………………………
使用代码5,添加一句打印语句。
然后使用指令3编译新的test.o文件,使用指令6,得到如下信息:
test.o: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000002d 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000008 00000000 00000000 00000064 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 0000006c 2**2 ALLOC 3 .rodata 00000004 00000000 00000000 0000006c 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .comment 00000028 00000000 00000000 00000070 2**0 CONTENTS, READONLY 5 .note.GNU-stack 00000000 00000000 00000000 00000098 2**0 CONTENTS, READONLY 6 .eh_frame 00000038 00000000 00000000 00000098 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA与之前的内容相比,发现多出一个字段.rodata。这个字段占用4个字节。
使用指令9,查看.rodata段中的信息,得到如下:
test.o: file format elf32-i386 Contents of section .text: 0000 5589e583 e4f083ec 20c74424 1c640000 U....... .D$.d.. 0010 00a10400 00008944 2404c704 24000000 .......D$...$... 0020 00e8fcff ffffb800 000000c9 c3 ............. Contents of section .data: 0000 64000000 e8030000 d....... Contents of section .rodata: 0000 25640a00 %d.. Contents of section .comment: 0000 00474343 3a202847 4e552920 342e372e .GCC: (GNU) 4.7. 0010 31203230 31323037 32312028 70726572 1 20120721 (prer 0020 656c6561 73652900 elease). Contents of section .eh_frame: 0000 14000000 00000000 017a5200 017c0801 .........zR..|.. 0010 1b0c0404 88010000 1c000000 1c000000 ................ 0020 00000000 2d000000 00410e08 8502420d ....-....A....B. 0030 0569c50c 04040000 .i......可以使用man查看ascii,可以查到如下ASCII码关系:
Oct Dec Hex Char 045 37 25 % 144 100 64 d 012 10 0A LF '\n' (new line)对应.rodata字段中的信息,明显存的就是“%d\n”这三个符号了。
注:第四个字节没有使用是因为字节对齐的原因。
ELF中字段很多,甚至还可以自定义,如果掌握了如上的分析方法,那分析其他段就相对容易了。
指令1、-E:只进行预处理,并把处理结果输出
$ gcc -E test.c -o test.i或者:
$ cpp test.c > test.i
指令2:-S:只进行汇编
从预处理文件到汇编文件
$ gcc -S test.i -o test.s或者:从C文件到汇编文件
$ gcc -S test.c -o test.s
指令3:
从汇编源码编译
$ as test.s -o test.o或者:-c:只编译,不链接 ,
$ gcc -c test.c -o test.o
指令4:
$ hexdump -C test.o
指令5、查看文件类型
$ file *
指令6:-h : 查看各个段的基本信息
$ objdump -h test.o
指令7:-h:目标文件首部的详细信息
$ readelf -h test.o
指令8:-d : 反汇编
$ objdump -d test.o
$ objdump -s test.o
$ objdump -x test.o
指令11:
readelf -S test.o
$ readelf -s test.o
指令13:
$objdump -t test.o
代码1、
/*test.c*/ #ifndef NUM #define NUM 100 #endif int main() { /*this is test.cc*/ int a = NUM; return 0; }
代码2、
/*test.c*/ #ifndef NUM #define NUM 100 #endif int gloabl_var = 100; /*全局变量*/ int main() { /*this is test.cc*/ int a = NUM; return 0; }
/*test.c*/ #ifndef NUM #define NUM 100 #endif int gloabl_var = 100; /*全局变量*/ int main() { /*this is test.cc*/ int a = NUM; static int b = 1000;/* 局部静态变量*/ return 0; }
代码4:
/*test.c*/ #ifndef NUM #define NUM 100 #endif int gloabl_var = 100; /*全局变量*/ int global_var2; /* 未初始化的全局变量*/ int main() { /*this is test.cc*/ int a = NUM; static int b = 1000;/* 局部静态变量*/ static int c; /*未初始化的静态变量*/ return 0; }
代码5、
/*test.c*/ #ifndef NUM #define NUM 100 #endif int gloabl_var = 100; /*全局变量*/ /*int global_var2; [> 未初始化的全局变量<]*/ int main() { /*this is test.cc*/ int a = NUM; static int b = 1000;/* 局部静态变量*/ /*static int c; [>未初始化的静态变量<]*/ printf("%d\n", b); /*.rodata字段*/ return 0; }