分析以下代码中变量存储空间如何分配:

 //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语言变量的存储布局的更多相关文章

  1. c语言 变量的存储类别以及对应的内存分配?

    <h4><strong>1.变量的存储类别</strong></h4>从变量值存在的角度来分,可以分为静态存储方式和动态存储方式.所谓静态存储方式指在程 ...

  2. [汇编与C语言关系]3. 变量的存储布局

    以下面C程序为例: #include <stdio.h> ; ; ; int c; int main(void) { ; char b[] = "Hello World" ...

  3. C语言变量的存储类别

    我们知道,从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量. 从另一个角度,从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式. 静态存储方式:是指在程序运行期 ...

  4. C语言中变量的存储类型

    在C语言中,对变量的存储类型说明有以下四种: auto 自动变量register   寄存器变量extern 外部变量static   静态变量 自动变量和寄存器变量属于动态存储方式,外部变量和静态变 ...

  5. C语言变量的类型和存储位置

    . C语言变量主要分为全局变量.静态全局变量.局部变量.静态局部变量和寄存器变量.其中静态变量用static关键字进行修饰.程序所占用的内存可以分为以下几个部分: ()代码段-存放程序代码,只读的,不 ...

  6. C语言变量、函数的作用域及变量的存储方式

    一.变量的作用域和存储方式 在C语言中每个变量都有两种基本属性:数据类型.数据的存储类别. 数据类型很多人都已熟知,例如:字符型(char).整型(int).浮点型(float)等等.存储类别是指数据 ...

  7. C语言学习笔记:12_变量的存储方式和生存期

    /* * 12_变量的存储方式和生存期.c * * Created on: 2015年7月5日 * Author: zhong */ #include <stdio.h> #include ...

  8. Go 语言变量

    变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念.变量可以通过变量名访问. Go 语言变量名由字母.数字.下划线组成,其中首个字母不能为数字. 声明变量的一般形式是使用 var 关键字: ...

  9. GO语言学习(七)Go 语言变量

    Go 语言变量 变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念.变量可以通过变量名访问. Go 语言变量名由字母.数字.下划线组成,其中首个字母不能为数字. 声明变量的一般形式是使用 ...

随机推荐

  1. 第三百四十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—Requests请求和Response响应介绍

    第三百四十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—Requests请求和Response响应介绍 Requests请求 Requests请求就是我们在爬虫文件写的Requests() ...

  2. JDBC查询数据实例

    在本教程将演示如何在JDBC应用程序中,查询数据库的一个表中数据记录. 在执行以下示例之前,请确保您已经准备好以下操作: 具有数据库管理员权限,以在给定模式中数据库表中查询数据记录. 要执行以下示例, ...

  3. e811. 创建具有嵌套菜单的弹出式菜单

    See e810 创建弹出菜单 for an example on how to display a popup menu. final JPopupMenu popupMenu = new JPop ...

  4. ssh命令详解3

    SSH 的详细使用方法如下: ssh [-l login_name] [hostname | user@hostname] [command] ssh [-afgknqtvxCPX246] [-c b ...

  5. C# 抓取网页的img src带参数的图片链接,并下载

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. 浅谈你感兴趣的 CLR GC 机制底层

    本文内容是学习CLR.via C#的21章后个人整理,有不足之处欢迎指导. 昨天是1024,coder的节日,我为自己coder之路定下一句准则--保持学习,保持自信,保持谦逊,保持分享,越走越远. ...

  7. 安装Java Decompiler

    原文:https://blog.csdn.net/yh_zeng2/article/details/75948467 Java Decompiler是Java语言的反编译工具,具体介绍见博客Java ...

  8. android中YUV转RGB的方法

    在一个外国网站上看到一段YUV转RGB的程序很不错,根据维基上的知识,方法应该是没问题的,自己也用过了,效果没问题. 首先说一下android上preview中每一帧的信息都是YUV420的,或者叫N ...

  9. VS2010 工程设置

       本篇文章的主要内容转载自 http://blog.csdn.net/waitforfree/article/details/8622059 ,感谢博主的辛苦劳动.此处,对比较重要的部分,进行进一 ...

  10. geoserver PostGIS的安装和使用

    PostGIS是一个非常流行并且开源的具有空间分析能力的关系型数据库,它作为PostgreSQL数据库的一个插件.PostgreSQL是一个功能非常强大并且开源的关系型数据库.目前项目使用的版本为Po ...