一起talk C栗子吧(第一百三十回:C语言实例--C程序内存布局二)

2024-03-12 04:58

本文主要是介绍一起talk C栗子吧(第一百三十回:C语言实例--C程序内存布局二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


各位看官们,大家好,上一回中咱们说的是C程序内存布局的例子,这一回咱们继续说该例子。闲话休提,言归正转。让我们一起talk C栗子吧!

看官们,我们在上一回中介绍了C程序在内存中的布局,并且给大家做了简单的演示。上一回的例子比较简单,只能说明程序中内存布局的大体轮廓。我们今天会通过具体的内存地址来清楚地介绍C程序在内存中的布局。下面是例子的程序源代码,请大家参考:

int ga1;
int ga2 = 1;int func()
{int i = 0;static static_la1;static static_la2 = 3;printf("func is running \n");printf("Address of i: %p \n",&i);printf("Address of static_la1 : %p \n",&static_la1);printf("Address of static_la2 : %p \n",&static_la2);while(i++<8)sleep(1);return 0;
}int main()
{int la1;int la2 = 2;int *p;p = (int *) malloc(3*sizeof(int));if(NULL == p)printf("malloc failed \n");printf("Address of ga1: %p \n",&ga1);printf("Address of ga2: %p \n",&ga2);printf("Address of la1: %p \n",&la1);printf("Address of la2: %p \n",&la2);printf("Address of p: %p \n",p);func();free(p);p = NULL;return 0;
}

在该代码中,我们手动输出各个变量的地址,这样做的目的是为了通过变量的地址来判断变量在内存中的位置,进而确认变量属于内存布局中的哪个分区。那么这些变量究竟是在哪个分区中呢?data分区还是bss分区?下面是程序的运行结果,请大家参考:

Address of ga1: 0x804a040 
Address of ga2: 0x804a030 
Address of la1: 0xbfc7e7b4 
Address of la2: 0xbfc7e7b8 
Address of p: 0x8ffc008 
func is running 
Address of i: 0xbfc7e78c 
Address of static_la1 : 0x804a03c 
Address of static_la2 : 0x804a034 

从程序的运行结果中我们可以看到,各个变量在内存中的地址,不过,我们还是不知道这些变量属于哪个内存分区。看官们莫急,我们通过readelf工具来查看该程序的内存布局,通过内存布局就可以看到各个内存分区的地址范围,这些我就们就能依据变量的地址推断出变量所在的内存分区。下面是详细的结果:

readelf -S s            //使用readelf 工具查看程序的内存布局
There are 30 section headers, starting at offset 0x1190:Section Headers:[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al[ 0]                   NULL            00000000 000000 000000 00      0   0  0[ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1[ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4[ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4[ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4[ 5] .dynsym           DYNSYM          080481cc 0001cc 000090 10   A  6   1  4[ 6] .dynstr           STRTAB          0804825c 00025c 000063 00   A  0   0  1[ 7] .gnu.version      VERSYM          080482c0 0002c0 000012 02   A  5   0  2[ 8] .gnu.version_r    VERNEED         080482d4 0002d4 000020 00   A  6   1  4[ 9] .rel.dyn          REL             080482f4 0002f4 000008 08   A  5   0  4[10] .rel.plt          REL             080482fc 0002fc 000038 08   A  5  12  4[11] .init             PROGBITS        08048334 000334 000023 00  AX  0   0  4[12] .plt              PROGBITS        08048360 000360 000080 04  AX  0   0 16[13] .text             PROGBITS        080483e0 0003e0 0002a2 00  AX  0   0 16[14] .fini             PROGBITS        08048684 000684 000014 00  AX  0   0  4[15] .rodata           PROGBITS        08048698 000698 0000dc 00   A  0   0  4[16] .eh_frame_hdr     PROGBITS        08048774 000774 000034 00   A  0   0  4[17] .eh_frame         PROGBITS        080487a8 0007a8 0000d0 00   A  0   0  4[18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4[19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4[20] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4[21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4[22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4[23] .got.plt          PROGBITS        0804a000 001000 000028 04  WA  0   0  4[24] .data             PROGBITS        0804a028 001028 000010 00  WA  0   0  4[25] .bss              NOBITS          0804a038 001038 00000c 00  WA  0   0  4[26] .comment          PROGBITS        00000000 001038 00004f 01  MS  0   0  1[27] .shstrtab         STRTAB          00000000 001087 000106 00      0   0  1[28] .symtab           SYMTAB          00000000 001640 0004c0 10     29  47  4[29] .strtab           STRTAB          00000000 001b00 0002be 00      0   0  1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings)I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)O (extra OS processing required) o (OS specific), p (processor specific)

从运行结果中可以看到程序的

data分区从地址0x0804a028开始,大小为0x000010(16byte)。
bss分区从0x0804a038开始,大小为0x00000c(12byte)。

那么我们结合程序中变量的地址来推断一下变量所在的内存分区:

Address of ga1: 0x804a040    //变量的地址大于bss(0x0804a038),变量位于bss分区
Address of ga2: 0x804a030    //变量的地址大于data(0x0804a028),而且小于bss,变量属于data分区
Address of static_la1 : 0x804a03c    //变量的地址大于bss(0x0804a038),变量位于bss分区
Address of static_la2 : 0x804a034    //变量的地址大于data(0x0804a028),而且小于bss,变量属于data分区

还有四个变量的地址不在bss和data分区范围内,因此不能确定这些变量所在的分区:

Address of la1: 0xbfc7e7b4   //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围
Address of la2: 0xbfc7e7b8   //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围
Address of p: 0x8ffc008      //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围
Address of i: 0xbfc7e78c     //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围

我们在前一章回中介绍过,重要的分区就四种,他们不在data和bss,那么只能在堆区或者栈区了。现在我们只有变量的地址,要是有堆区或者栈区的地址范围就好了。这样我们就可以向刚才推断bss和data分区中的变量一样来推断这些变量在内存中的分区。

接下我们一起查找一下程序的栈和堆分区。还记得我们在前面章回中介绍的proc虚拟文件系统吗?我们可以借助它来获得栈区和堆区的内存空间信息。

 ./s &                               //在后台运行编译好的程序
[1] 2736                                                                                    //程序在后台正常运行,同时显示程序的PID
Address of ga1: 0x804a040            //程序运行,输出程序中的内容
Address of ga2: 0x804a030 
Address of la1: 0xbfe01cf4 
Address of la2: 0xbfe01cf8 
Address of p: 0x964d008 
func is running 
Address of i: 0xbfe01ccc 
Address of static_la1 : 0x804a03c 
Address of static_la2 : 0x804a034 
cat /proc/2736/maps                   //查看proc中关于进程的信息
08048000-08049000 r-xp 00000000 08:13 22415696   /home/talk8/s
08049000-0804a000 r--p 00000000 08:13 22415696   /home/talk8/s
0804a000-0804b000 rw-p 00001000 08:13 22415696   /home/talk8/s
0964d000-0966e000 rw-p 00000000 00:00 0          [heap]  
b75b7000-b75b8000 rw-p 00000000 00:00 0 
b75b8000-b7761000 r-xp 00000000 08:16 6444       /lib/i386-linux-gnu/libc-2.19.so
b7761000-b7763000 r--p 001a9000 08:16 6444       /lib/i386-linux-gnu/libc-2.19.so
b7763000-b7764000 rw-p 001ab000 08:16 6444       /lib/i386-linux-gnu/libc-2.19.so
b7764000-b7767000 rw-p 00000000 00:00 0 
b777e000-b7781000 rw-p 00000000 00:00 0 
b7781000-b7782000 r-xp 00000000 00:00 0          [vdso]
b7782000-b77a2000 r-xp 00000000 08:16 6459       /lib/i386-linux-gnu/ld-2.19.so
b77a2000-b77a3000 r--p 0001f000 08:16 6459       /lib/i386-linux-gnu/ld-2.19.so
b77a3000-b77a4000 rw-p 00020000 08:16 6459       /lib/i386-linux-gnu/ld-2.19.so
bfde3000-bfe04000 rw-p 00000000 00:00 0          [stack]

从上面信息中我们可以看到,标记为[heap]和[stack]的分区就是我们要找的堆区和栈区,与其在同一行中的内容显示了这两个分区的内存地址空间。

堆区:0x0964d000-0x0966e000
栈区:0xbfde3000-0xbfe04000

现在,我们可以推断刚才哪四个不知道自己所在分区的变量了,下面是我们的推断结果:

Address of la1: 0xbfe01cf4   //变量的地址位于栈区地址空间内,该变量位于栈区
Address of la2: 0xbfe01cf8   //变量的地址位于栈区地址空间内,该变量位于栈区
Address of p: 0x964d008      //变量的地址位于堆区地址空间内,该变量位于堆区
Address of i: 0xbfe01ccc     //变量的地址位于栈区地址空间内,该变量位于栈区

看官们,到目前为止, 我们通过具体的内存地址清楚地展示了C程序在内存中的布局。最后我们画一个直观的图形给大家看,这样大家就能直观地看清楚C程序的内存模型了。
这里写图片描述
该图形比较简单,在图形中从上向下看,可以看到内存地址从高地址向低地址延伸,每个地址后面是与该地址对应的分区名称。

看官们,完整的代码放到了我的资源中,大家可以点击这里下载使用(CSDN又出问题了,所以暂时不能上传程序,等CSDN修复好问题后,我会及时上传程序,到时候大家可以到我们资源中下载程序)。因为每个人的电脑是不同的,而且所使用的编程环境也可能不相同,所以建议各位看官都下载程序到自己的电脑上运行,然后按照我们介绍的方法观察程序在内存中的模型。

各位看官,关于C程序内存布局的例子咱们就说到这里。欲知后面还有什么例子,且听下回分解 。


这篇关于一起talk C栗子吧(第一百三十回:C语言实例--C程序内存布局二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/800196

相关文章

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

MySQL的索引失效的原因实例及解决方案

《MySQL的索引失效的原因实例及解决方案》这篇文章主要讨论了MySQL索引失效的常见原因及其解决方案,它涵盖了数据类型不匹配、隐式转换、函数或表达式、范围查询、LIKE查询、OR条件、全表扫描、索引... 目录1. 数据类型不匹配2. 隐式转换3. 函数或表达式4. 范围查询之后的列5. like 查询6

Python开发围棋游戏的实例代码(实现全部功能)

《Python开发围棋游戏的实例代码(实现全部功能)》围棋是一种古老而复杂的策略棋类游戏,起源于中国,已有超过2500年的历史,本文介绍了如何用Python开发一个简单的围棋游戏,实例代码涵盖了游戏的... 目录1. 围棋游戏概述1.1 游戏规则1.2 游戏设计思路2. 环境准备3. 创建棋盘3.1 棋盘类

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl