前言
今天开始分享C语言里面的存储类型、作用域、生命周期、链接属性等知识点,我们写完一个程序,不只说知其,更要知其所以然。
概念简介:
- 存储类 -
(1)存储类就是存储类型,也就是描述C语言变量在何种地方存储。
(2)内存有多种管理方法:栈、堆、数据段、bss段、.text段等,其实这个Linux环境可以查看以ELF结尾的可执行程序,可以看到所说的这些的;一个变量的存储类属性就是描述这个变量存储在何种内存段中。
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu# readelf hello-world -S
There are 29 section headers, starting at offset 0x1930:
节头:
[号] 名称 类型 地址
偏移量
大小 全体大小 旗标 链接 信息
对齐
[ 0] NULL 0000000000000000
00000000
0000000000000000 0000000000000000 0 0
0
[ 1] .interp PROGBITS
0000000000000238
00000238
000000000000001c 0000000000000000 A 0 0
1
[ 2] .note.ABI-tag NOTE
0000000000000254 00000254
0000000000000020 0000000000000000 A 0 0
4
[ 3] .note.gnu.build-i NOTE
0000000000000274 00000274
0000000000000024 0000000000000000 A 0 0
4
[ 4] .gnu.hash GNU_HASH
0000000000000298 00000298
000000000000001c 0000000000000000 A 5 0
8
[ 5] .dynsym DYNSYM 00000000000002b8 000002b8
00000000000000a8 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000360 00000360
0000000000000082 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000000003e2 000003e2
000000000000000e 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000000003f0 000003f0
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000410 00000410
00000000000000c0 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000000004d0 000004d0
0000000000000018 0000000000000018 AI 5 22 8
[11] .init PROGBITS 00000000000004e8 000004e8
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000000500 00000500
0000000000000020 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000000520 00000520
0000000000000008 0000000000000008 AX 0 0 8
[14] .text PROGBITS 0000000000000530 00000530
00000000000001a2 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000000006d4 000006d4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000000006e0 000006e0
0000000000000010 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000000006f0 000006f0
000000000000003c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000000730 00000730
0000000000000108 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000200db8 00000db8
0000000000000008 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000200dc0 00000dc0
0000000000000008 0000000000000008 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000200dc8 00000dc8
00000000000001f0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000200fb8 00000fb8
0000000000000048 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000201000 00001000
0000000000000010 0000000000000000 WA 0 0 8
[24] .bss NOBITS 0000000000201010 00001010
0000000000000008 0000000000000000 WA 0 0 1
[25] .comment PROGBITS 0000000000000000 00001010
000000000000002b 0000000000000001 MS 0 0 1
[26] .symtab SYMTAB 0000000000000000 00001040
00000000000005e8 0000000000000018 27 43 8
[27] .strtab STRTAB 0000000000000000 00001628
0000000000000209 0000000000000000 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00001831
00000000000000fe 0000000000000000 0 0 1
说明(这里的话,我简单介绍一些我们平时没有接触到的,大家只需了解即可):
- dynamic段:用于保存动态链接信息。
- fini段:用于保存进程退出时的执行程序。当进程结束时,系统会自动执行这部分代码。
- init段:用于保存进程启动时的执行程序。当进程启动时,系统会自动执行这部分代码。
- rodata段:用于保存只读数据,如const修饰的全局变量、字符串常量。
- symtab段:用于保存符号表。
(3)譬如:局部变量分配在栈上,所以它的存储类就是栈;显式初始化为非0的全局变量分配在数据段,显式初始化为0和没有显示初始化(默认为0)的全局变量分配在bss段。
- 作用域 -
(1)作用域是描述这个变量起作用的代码范围。
(2)基本来说,C语言变量的作用域规则是代码块作用域。意思就是这个变量起作用的范围是当前的代码块。代码块就是一对大括号{}括起来的范围,所以一个变量的作用域是:这个变量定义所在的{}范围内从这个变量定义开始往后的部分。(这就解释了为什么变量定义总是在一个函数的最前面)。而且当局部变量和全局变量同名的时候,在main函数里面是先执行局部变量的,也就是说局部变量的作用域是代码块作用域,也就是说一个局部变量可以被访问和使用的范围仅限于定义这个局部变量的代码块中定义式之后的部分:
代码语言:javascript复制 #include <stdio.h>
int a=7;
int main(void)
{
int a=8;
a ;
printf("the a is %dn",a);
return 0;
}
演示结果:
代码语言:javascript复制the a is 9
-生命周期-
(1)声明周期是描述这个变量什么时候诞生(运行时分配内存空间给这个变量)及什么时候死亡(运行时收回这个内存空间,此后再不能访问这个内存地址,或者访问这个内存地址已经和这个变量无关了)的。
(2)变量和内存的关系,就和人(变量)去图书馆借书(内存)一样。变量的生命周期就好象我人借书的这段周期一样。
(3)研究变量的生命周期可以我们理解程序运行的一些现象、理解C语言的一些规则。
- 链接属性 -
(1)大家知道程序从源代码到最终可执行程序,经历的过程:编译、链接。
(2)编译阶段就是把源代码搞成.o目标文件,目标文件里面有很多符号和代码段、数据段、bss段等分段。符号就是编程中的变量名、函数名等。运行时变量名、函数名能够和相应的内存对应起来,靠符号来做链接的。
(3).o的目标文件链接生成最终可执行程序的时候,其实就是把符号和相对应的段给链接起来。
(4)C语言中的符号有三种链接属性:外连接属性、内链接属性、无连接属性(这里只是简单介绍一些有几种链接属性,后面的文章里面会详细的介绍这些链接属性)。
Linux下c程序的内存映像
- 代码段、只读数据段 -
(1)对应着程序中的代码(函数),代码段在linux中又叫文本段(.text)。
(2)只读数据段就是在程序运行期间只能读不能写的数据,const修饰的常量有可能是存在只读数据段的(但是不一定,const常量的实现方法在不同平台是不一样的)。
- 数据段、bss段 -
(1)数据段存:显式初始化为非0的全局变量;显式初始化为非0的static局部变量。
(2)bss段存:显式初始化为0或者未显式初始化的全局变量;显式初始化为0或未显式初始化的static局部变量。这里详细可以看之前写的这篇文章——轻松带你解决c语言堆、栈、数据段、代码段、bss段的疑惑
- 堆 -
(1)C语言中什么样变量存在堆内存中?C语言不会自动向堆中存放东西,堆的操作是程序员自己手工操作的。程序员根据需求自己判断要不要使用堆内存,用的时候自己申请(使用malloc函数),自己使用,完了自己释放(使用free函数释放掉)。
- 文件映射区 -
(1)文件映射区就是进程打开了文件后,将这个文件的内容从硬盘读到进程的文件映射区,以后就直接在内存中操作这个文件,读写完了后在保存时再将内存中的文件写到硬盘中去。
- 栈 -
(1)栈内存区,局部变量分配在栈上;函数调用传参过程也会用到栈。
- 内核映射区 -
(1)内核映射区就是将操作系统内核程序映射到这个区域了。
(2)对于linux中的每一个进程来说,它都以为整个系统中只有它自己和内核而已。它认为内存地址0xC0000000以下都是它自己的活动空间,0xC0000000以上是OS内核的活动空间。
(3)每一个进程都活在自己独立的进程空间中,0-3G的空间每一个进程是不同的(因为用了虚拟地址技术),但是内核是唯一的。
总结
上面的介绍,只是一些概念性的介绍,要详细更加深入的理解Linux内存,可以看这篇文章:https://blog.csdn.net/f22jay/article/details/7925531