说明:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
QQ 群 号:513683159 【相互学习】
内容来源:
《系统程序员成长计划》
上一篇:读书笔记 ——《系统程序员成长计划》篇4:拥抱变化
下一篇:读书笔记 ——《系统程序员成长计划》篇6:写的又快又好的秘诀
目录:
一、数据放在哪里?
书中以双链表几种方式存字符串为示例演示由于不明白数据的存放位置导致代码出错或存在问题(详细查看书籍)。
关于内存布局也可查看:C语言——C程序的内存空间布局
二、内存段
1、未初始化的全局变量(.bss段)
1️⃣示例引入
int bss_array[1024 * 1024];
int main(int argc, char* argv[])
{
return 0;
}
编译生成可执行文件:# gcc -g bss.c -o bss.exe
查看文件大小:# ls -l bss.exe
-rwxr-xr-x 1 root root 17656 Apr 18 21:29 bss.exe
可知:文件大小大概在17k左右。
查看执行时文件大小:# objdump -h bss.exe|grep bss
bss.exe: file format elf64-x86-64
23 .bss 00400020 0000000000004020 0000000000004020 00003010 2**5
可知:文件大小大概在400k左右。
由此可见,bss类型的全局变量只占运行内存,不占文件内存。
2️⃣指令说明:
详细请查看:gcc -g -o -c分别是什么意思、objdump选项说明、objdump、 objdump 二进制文件分析。
gcc -g
:可执行程序包含调试信息
objdump -h 文件名
:显示目标文件各个section的头部摘要信息
|
管道符,格式:命令1|命令2|...
:管道符左边命令的输出作为管道符右边命令的输入,依此类推。
grep
查找文件中符号条件的字符串
3️⃣bss 简述:
bss段被用来存放那些没有初始化或初始化为0的全局变量。
bss类型的全局变量只占运行的内存,而不占用文件空间。
现在大多数操作系统在加载程序时,会把所有bss全局变量清零。但最好手动初始化为0.
作为全局变量,在整个程序运行周期内,bss数据是一直存在的。
2、初始化的全局变量(.data段)
1️⃣ 示例引入
int bss_array[1024 * 1024]= {1};
int main(int argc, char* argv[])
{
return 0;
}
与前面的区别就是该全局变量进行初始化了。当然若初始化的数据全为0为了优化考虑会将其当做bss处理。
编译生成可执行文件:# gcc -g data.c -o data.exe
查看文件大小:# ls -l data.exe
-rwxr-xr-x 1 root root 4211984 Apr 18 22:07 data.exe
可知:文件大小大概在4M左右。
查看执行时文件大小:# objdump -h data.exe|grep \\.data
22 .data 00400020 0000000000004000 0000000000004000 00003000 2**5
可知:文件大小大概在400k左右。
即:data类型的全局变量既占文件空间,又占用运行时内存空间,同样作为全局变量,在整个程序运行周期内,data数据也一直存在。
2️⃣ data 简述:
data段用来存放着数据,被用来存放那些初始化非0值的全局变量。
若数据全是0,则为了优化考虑,会当作bss处理。
data类型的全局变量既占文件空间,又占运行内存空间。
作为全局变量,在整个程序运行周期内,data数据是一直存在的。
3、常量数据(.rodata段)
ro:read only(只读),用来存放常量数据。
需注意几点:
(1)常量不一定就放在rodata里,有的立即数直接和指令编码在一起,存放在代码段(.text)中。
(2)对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一个副本。
(3 ) rodata是在多个进程间共享的,这样可以提高运行空间利用率。
(4)在有的嵌入式系统中,rodata放在ROM(或者NOR闪存芯片)里,运行时直接读取,无需加载到RAM中。
(5)在嵌入式Linux系统中,也可以通过一种叫作XIP(就地执行)的技术直接读取常量数据,而无需加载到RAM中。
(6)常量是不能修改的,修改常量在Linux下会出现段错误。
由此可见,把在运行过程中不会改变的数据设为rodata类型是有好处的。在多个进程间共享,可以大大提高空间利用率,甚至能不占用RAM空间。同时由于rodata在只读的内存页面中是受保护的,任何试图对它进行修改的行为都会被及时发现,这样一来还可以提高程序的稳定性。
字符串会被编译器自动放到rodata中,其他数据要放到rodata中,只需要为其加const关键字修饰即可。
4、代码(.text段)
text段存放代码(如函数)和部分整数常量,它与rodata段很相似,相同的特性我们就不重复了,主要的区别在于text段是可以执行的。
5、栈(stack)
存放临时变量和函数参数。
常用来实现函数调用和递归操作。
栈是向下(低地址)增长的,每向栈中PUSH一个元素,栈顶就向低地址扩展,每从栈中POP一个元素,栈顶就向高地址回退。
要注意,存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也就自动释放了,继续访问这些变量会造成意想不到的错误。
6、堆(heap)
最灵活的一种内存,生命周期完全由使用者控制,标准C提供一下几个函数使用堆内存:
函数名 用途
malloc
分配一块指定大小的内存
realloc
调整/重分配一块存在的内存
free
释放不再使用的内存。
需注意:
alloc/free
要配对使用,否则分配内存而不释放的话会出现内存泄露(memory leak)。过多则会出现内存不足(Out of memory)。
分配多少用多少,否则若读多了,会读到随机数据。若写多了,则会造成随机破坏。而这种情况被称为缓冲区溢出(buffer overflow)。
手动检查有无内存泄露或缓冲区溢出是困难的,但可使用工具,如:valgrind。