|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
根据C语言的特点,每一个源程序生成的目标代码将包含源程序所需要表达的所有信息和功能。有些时候很有必要从这些段中来分析实际使用情况和改进空间。目标代码中各段生成情况如下:
7 \! @0 @/ J& Z7 \5 {
$ q2 b5 ^: q7 y& R5 P1、代码段(Code)
0 Z& N# h9 H) F0 C: j代码段由程序中各个函数产生,函数的每一个语句将最终经过编译和汇编生成二进制机器代码(具体生成哪种体系结构的机器代码由编译器决定)
9 A8 X; w2 y: G0 _3 @ u( C4 L" I2 t, R% a9 a
2、只读数据段(RO Data)
* j" G: ^, \$ U+ I3 K8 a. S/ `只读数据段由程序中所使用的数据产生,该部分数据的特点是在运行中不需要改变,因此编译器会将该数据放入只读的部分中。C语言的一些语法将生成只读数据段。! ~( {* r1 ?1 q; b9 c4 }# g
9 c3 j4 G% p; J; S9 V4 k
① 只读全局变量' d! ]; U6 P# k) q7 \% Q; Y
例如:定义全局变量 const char a[100]={“ABCDEFG”};5 a0 t. G" {+ F: O% ?; \
这个是生成大小为100个字节的只读数据区,并使用字符串“ABCDEFG”初始化。如果定义的时候没有指定大小,那么根据初始化的字符串长度生成相应大小的只读数据段。( X5 V- Q! p1 F- j- u6 B
1 {, y# y9 r( J& P O) @② 只读局部变量
9 j1 \' |# L& g. ]) `9 Y4 P2 g例如:在函数内部定义的只读变量 const char b[100]={“9876543210”};
. R h# m5 Z/ N$ V9 y1 s
* a5 r% X' b- f- G/ W3 e③ 程序中使用的常量 L+ t" D7 L. L+ M9 u! V' K& U
例如:在程序中使用printf(“information \n”),其中“information \n”是字符串常量, 编译器会自动把常量放入只读数据区。
, x' d1 X. E- j9 X& R) L! U* E" E4 h$ p1 \ P
注意:上面两个变量定义,定义100个大小的区域,但是只初始化前面几个字节,实际后面的字节没有初始化,但是在程序中也不能写,实际是没有任何用处的。所以定义只读的时候需要做完全的初始化。
" K0 j7 ^' z# p
& u' L! M( b7 q( ~- R. L3、读写数据段(RW Data)# S3 J$ U4 k7 r$ R+ A% }
读写数据段表示了在目标文件中一部分可以读也可以写的数据区,在某些场合它们又被称为已初始化数据段。这部分是属于程序中的静态区域。3 @9 y) `2 F3 P/ b" J1 |7 @
; |9 C0 T; o0 n2 F" s①已初始化全局静态变量
2 Q6 | [9 S4 E: n) W在函数外部定义的全局的变量,并且初始化。(static是限制作用域的)
% X% q" G J4 e+ a2 p1 b# ?! A0 b$ K* p x8 G1 W+ J# E, c
②已初始化局部静态变量
9 ]" K9 e% {; q# t9 U" M在函数中定义的由static定义并且已经初始化的数据或者数组。2 ^ v Y' U* i8 O- S6 S R
注意:定义的变量要有初始化才会在读写数据区。
C* o/ V! n- f$ Q+ |$ Q* r' j' ^8 x2 X/ A! |: X q# j% s
4、未初始化数据段(BSS)& k) X! `3 Q! a+ ~1 U* B+ j G
这个段也属于静态数据区。但是没有初始化,所以在目标文件中会有标识,而不会真正称为目标文件中的一个段,这个段会在运行时产生,所以它的大小不会影响目标文件的大小。
8 N; |( J3 P" L3 ]0 x1 {6 `0 X![]()
" M4 N: l! L% k8 k- e( B( R比如上面这个图就是通常我们编译后获得的。当你的方案选型是一个空间很小的处理器的时候很有必要了解这些存储的区域都存的是什么,方便处理冗余或者修改方案。3 }9 n% C" h2 J, ]3 ]/ \$ N. b
9 |( S. b7 j5 u8 X上面我们了解了程序对应的存储空间,程序是怎么对号入座到这些存储区的呢?一起来看下吧,也没想象的那么神秘。: O+ ~* w. K% ?! w
4 j+ |/ U* x8 Z3 @, Y: O
我们每一个C语言源程序(*.c)经过编译生成目标文件(.o),目标文件就包含前面我们说的代码段、只读数据段、读写数据段。未初始化数据段、堆和栈不会占用目标文件的空间。& Y" y% O! V% L" W- J1 g+ w
5 }+ u- K+ c* y- {; a* I/ b3 E2 {
那么可执行程序是由各个目标文件经过链接而成,链接就是把各个目标文件的代码段、只读数据段、读写数据段经过了重新的排列组合。
, W$ ?2 R1 n; u, w0 N! e/ U2 B) u: ^8 B# Y9 A
需要注意的是未初始化数据段是怎么样的,在链接的过程中,链接器可以得到未初始化数据段的大小,它也是各个目标文件的各个未初始化数据段之和,但是这个段是不影响可执行程序大小的。在C语言使用的角度,读写数据段和未初始化数据段都是可读写的。实质上,在目标文件中未初始化数据段和读写数据段的区别也在于此,读写数据段占用目标文件的容量,而未初始化数据段只是一个标识,不需要占用实际的空间。9 X3 F$ ]+ ]' M7 L9 \; _
: _9 v' ~3 l# u! A; s5 q8 E: K- V6 s4 P在链接过程之前,各个源文件生成目标文件相互没有关系。在链接之后,各目标文件函数和变量可以相互调用和访问,从而被联系在一起。比如函数调用,链接过程就是要有函数调用的地方还需要找到真正的函数定义才可以完成链接,链接器会根据需要根据实际的情况修改编译器生成的机器代码,完成正确的跳转。全局变量的访问也是同理。' ?6 X7 y( i& A5 u
1 k, S' _" b& I. q1 ^5 C
再来了解下链接过程中常见的错误:
3 n) v; s M" u$ M" @" d" M, ~7 ^9 v U
; H. F9 Y5 i$ E2 o$ o7 D0 u1、符号未找到7 m% G3 n: {/ M; x' T& ~6 s
(1) 只要符号被声明,编译就可以通过,但是在链接过程中符号必须具有具体的实现才可以成功链接。 G; f' q, ~3 E" W$ a" M2 A3 h
(2) 由于数据仅能在文件内部使用(static),导致符号未定义错误。3 u% ?0 X+ J% [9 k/ }/ j
9 X/ j; v0 d9 d8 A" }' y2、符号重定义
3 T+ e) j4 t Q0 V9 Q(1) 在多个文件中定义全局的同名函数和变量(static的重名了是正确的)。4 J; P/ V, M {# J% Y
(2) 在头文件中定义已经初始化数据,在头文件被多个文件包含的时候,将发生错误。同样在头文件中也不应该定义只读数据段的常量。! ~5 W' W4 F* t l3 x
! N, N& A8 X& k) k8 R4 L4 }再有在头文件中不应该使用静态的变量,无论它有没有初值,这样虽然不会引起链接错误,但是在各个源文件中各自产生变量,不但占用空间,而且在逻辑上是不对的,也违背了头文件的使用原则。
+ L3 P! e/ I2 s' t8 o: f$ ^" Z9 Q: | m. V! W. `
从C语言程序设计的角度,不应该在头文件中定义变量或者函数。对于函数,在头文件中只是声明,需要在源文件中定义;对于变量,无论何种性质,最好的方式是在C语言的源文件中定义,在头文件中使用extern声明使用。* G: u1 F |5 r/ ]: K
3 S9 c7 X$ h! `# E
编译,链接后面就是执行了,后面我会跟大家再分享程序运行过程,这个其实都是C语言定的一些规则,只要守规则就会顺利完成想要实现的结果。
3 Y9 H. x5 I* y7 a8 T: c, b+ Y* n. V8 o- r
5 g0 }: t) Q' O( W9 ^
' X& ~6 i% ~ c6 n3 w/ m
; P+ _" F! X# }8 h+ w |7 q" C$ \+ E# ], i
|
|