C语言变量的存储布局
分析以下代码中变量存储空间如何分配:
//MemSeg.c: 代码无意义,仅供分析用
#include <stdio.h>
#include <stdlib.h> //malloc函数声明位于<stdlib.h>或<cstdlib>头文件中
#include <string.h> #define LEN_TEST_INT 4
#define LEN_TEST_CHAR 20 int gBufInitZero[LEN_TEST_INT] = {};
int gBufInitNonZero[LEN_TEST_INT] = {0xA, 0xB, 0xC, 0xD};
int gBufUnInit[LEN_TEST_INT]; const int gcVarInitNonZero = ; //const变量定义时必须初始化,因为定义后再不能改
int gVarInitZero = ;
int gVarInitNonZero = ;
int gVarInitUnInit;
static int gsVarInitZero = ;
static int gsVarInitNonZero = ;
static int gsVarUnInit; char gszStrInited[] = "STR_INITED";
char *gpszStrInited = "PSZ_INITED"; int main(void){
static int sVarInitNonZero = ;
char szStrInited[] = "Hello World";
register int rVarInitNonZero = ; char *pszChar = (char *)malloc(LEN_TEST_CHAR); //为简便起见未考虑内存分配错误处理
char *pszCharNext = (char *)malloc(LEN_TEST_CHAR); memset(pszChar, , LEN_TEST_CHAR);
strcpy(pszChar, szStrInited);
printf("pszChar: %p(%s), rVarInitNonZero:%d\n", pszChar, pszChar, rVarInitNonZero);
free(pszChar);
free(pszCharNext); return ;
}
MemSeg
编译生成a.out文件(gcc MemSeg.c -g),通过readelf命令(readelf -a a.out)查看目标文件中各变量的地址分布。截图中为突出重点对符号表结果进行了重排和截取:

图中,Num列是符号表序号(已重新排列)。Value列是符号地址,对于可重定位目标文件表示距定义目标文件的节(Section)起始位置的偏移,对于可执行目标文件表示绝对运行地址。Size列为符号字节大小。Type列指示数据(OBJECT)、函数(FUNC)或节(SECTION)等类型。Bind列表明符号的绑定信息,分为局部(对于目标文件的外部不可见)、全局(外部可见)和弱引用(WEAK)。Ndx列为数字时表示节索引,为ABS时代表不该被重定位的符号(如文件名),UND代表定义在别处的符号(如printf库函数),COM代表未初始化数据目标(如某些编译器的未初始化全局变量)。Name列为符号名(图中所示为变量名)。
全局变量gcVarInitNonZero用const修饰,表明不应修改其值。该变量被分配0x80485b0地址,从readelf输出可知该地址位于.rodata段(局部只读变量位于栈区):

该段在文件中的地址是0x5a4~0x5f1(0x5a4+0x4d)。用hexdump命令查看该段内容:

其中0x5a0地址处的0a 00 00 00(小字节序)即变量gcVarInitNonZero的值。也可看到字符串字面值"PSZ_INITED"、"pszChar: %p(%s)\n"及"Hello World"分配在.rodata段。
.data段从地址0x80496fc开始,长度是0x30,即到地址0x804972c结束。.data段存放初值不为0的非const全局变量或静态局部变量。gVarInitNonZero是个GLOBAL符号,而gsVarInitNonZero被static关键字修饰而成为LOCAL符号(不被链接器处理,即不能被其他文件引用)。静态局部变量sVarInitNonZero.2443只在函数内起作用,故编译器对其符号名附加后缀,以便与同名的全局变量或其它函数的变量区分。
注意,若字符数组定义在函数外,则初始化字符串存放在.data段(如"STR_INITED");否则存放在.rodata段(如"Hello World")。——不解???
.bss段从地址0x804972c开始(紧挨.data段),长度为0x38,即到地址0x8049764结束。.bss段存放未初始化或初值为0的全局变量或静态局部变量,如gVarInitUnInit、gVarInitZero及gsVarUnInit等。
程序加载运行时,.rodata段和.text段通常合并为一个Segment,操作系统将该Segment页面只读保护起来,防止被意外改写。.data和.bss段在加载时也合并为一个Segment,该Segment可读可写。这点从readelf输出也可看出:

函数的参数和局部变量分配在栈上,故字符数组szStrInited也在栈区。如下反汇编代码所示:

可见,对栈区字符数组赋值时,利用寄存器从全局数据区把字符串拷贝到栈内存中。szStrInited数组的栈上内容从低地址到高地址依次为:Hell(ebp-0x1c)、o wo(ebp-0x18)、rld\0(ebp-0x14)。
栈区从高地址向低地址增长,但数组则从低地址向高地址排列,数组元素szStrInited[n]的地址 = 数组基地址(szStrInited做右值即表示基地址) + n × 每个元素的字节数。当n=0时,元素szStrInited[0]的地址即为数组基地址,因此数组下标从0开始。
变量rVarInitNonZero并未在栈上分配存储空间,而是直接存入ebx寄存器,后面调用printf直接从ebx寄存器里取出变量值当作参数压栈。因此,register关键字用于指示编译器尽可能分配一个寄存器来存储该变量。此外,调用printf时对格式化参数"pszChar: %p(%s), rVarInitNonZero:%d\n"压栈的是其.rodata段中的首地址(0x80485c0),并未压入整个字符串。故字符串使用时可视为数组名,若作为右值则表示数组首元素地址(即指向数组首元素的指针)。
pszChar和pszCharNext指针本身在栈上分配空间,但却指向malloc动态分配的堆内存。因为堆空间从低地址向高地址增长,故pszCharNext>pszChar(堆),而&pszCharNext<&pszChar(栈)。
最后,main函数的汇编指令存放在.text段。
【其他工具】
通过size命令可查看ELF文件各段占用的字节空间:

其中,dec和hex项分别为代码段、数据段和BSS段以十进制和16进制表示总字节数。
通过nm工具可查看按段分布的变量存储信息(比readelf结果易读):

通过objdump工具也可方便地查看目标文件中.rodata和.data段的内容(.bss段不写入目标文件):

objdump -t a.out的输出结果与nm –Ans类似。
此外,通过反汇编文件MemSeg.s(gcc -S MemSeg.c)也可查看.bss、.data和.text及栈区所存放的变量。
注:本文参考《Linux C编程一站式学习》一书相应章节,在此致谢!
C语言变量的存储布局的更多相关文章
- c语言 变量的存储类别以及对应的内存分配?
<h4><strong>1.变量的存储类别</strong></h4>从变量值存在的角度来分,可以分为静态存储方式和动态存储方式.所谓静态存储方式指在程 ...
- [汇编与C语言关系]3. 变量的存储布局
以下面C程序为例: #include <stdio.h> ; ; ; int c; int main(void) { ; char b[] = "Hello World" ...
- C语言变量的存储类别
我们知道,从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量. 从另一个角度,从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式. 静态存储方式:是指在程序运行期 ...
- C语言中变量的存储类型
在C语言中,对变量的存储类型说明有以下四种: auto 自动变量register 寄存器变量extern 外部变量static 静态变量 自动变量和寄存器变量属于动态存储方式,外部变量和静态变 ...
- C语言变量的类型和存储位置
. C语言变量主要分为全局变量.静态全局变量.局部变量.静态局部变量和寄存器变量.其中静态变量用static关键字进行修饰.程序所占用的内存可以分为以下几个部分: ()代码段-存放程序代码,只读的,不 ...
- C语言变量、函数的作用域及变量的存储方式
一.变量的作用域和存储方式 在C语言中每个变量都有两种基本属性:数据类型.数据的存储类别. 数据类型很多人都已熟知,例如:字符型(char).整型(int).浮点型(float)等等.存储类别是指数据 ...
- C语言学习笔记:12_变量的存储方式和生存期
/* * 12_变量的存储方式和生存期.c * * Created on: 2015年7月5日 * Author: zhong */ #include <stdio.h> #include ...
- Go 语言变量
变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念.变量可以通过变量名访问. Go 语言变量名由字母.数字.下划线组成,其中首个字母不能为数字. 声明变量的一般形式是使用 var 关键字: ...
- GO语言学习(七)Go 语言变量
Go 语言变量 变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念.变量可以通过变量名访问. Go 语言变量名由字母.数字.下划线组成,其中首个字母不能为数字. 声明变量的一般形式是使用 ...
随机推荐
- e798. 显示JSlider的标记标签
This example demonstrates how to display labels (numerical values) at the major ticks (see e797 显示JS ...
- 记一次艰难的IBM X3850重装系统和系统备份经验
[贴心话] 刚刚把一切都搞定了,回到电脑前立马就写下的这篇文章,写的很细节,大家就耐心看看,有些细节是网上没有的,共享一下,仅供参考,以减少大家装机时遇到的困难. [面临处境] 机器型号:IBM X3 ...
- Python资料收藏(杂乱版)
http://blog.csdn.net/vagrxie/article/category/343814 http://blog.sina.com.cn/s/articlelist_280149524 ...
- 前端图片压缩(纯js)
html代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <t ...
- java学习之maven
maven是项目构建工具,能把项目抽象成POM(Project Object Model) Maven使用POM对项目进行构建.打包.文档化等操作 解决了项目需要类库的依赖管理,简化了项目开发环境搭建 ...
- 详解MathType中如何更改公式颜色
在MathType数学公式编辑器中可以通过更改公式颜色,起到美化.标注公式的效果.本教程将详解MathType中如何更改公式颜色. 点击菜单栏中的样式->格式->颜色,用户就可以根据自己的 ...
- VC++ 链接错误LINK : fatal error LNK1104: cannot open file "*.lib"
问题描述: 运行VC++编译时经常出现 Linking… LINK : fatal error LNK1104: cannot open file “*.lib” Error executing li ...
- java用substring函数截取string中一段字符串
在String中有两个substring()函数,如下: 一:String.substring(int start) 参数: start:要截取位置的索引 返回: 从start开始到结束的字符串 例如 ...
- 防止dedecms注入文件挂马的解决方法
1.目录权限我们不建议用户把栏目目录设置在根目录,原因是这样进行安全设置会十分的麻烦,在默认的情况下,安装完成后,目录设置如下:(1) data.templets.uploads.a或5.3的html ...
- Docker安装指南
Ubuntu 14.04 16.04 (使用apt-get进行安装) # step 1: 安装必要的一些系统工具 sudo apt-get -y update sudo apt-get -y inst ...