在仔细研究这个问题之前,我认为 C 程序在内存中只有代码段,堆和栈三部分构成。前几天面试被问到了这个问题,才发现自己的印象是不完全的。

在本文中通过解析析一个 C 程序中变量和函数的地址来分析 C 程序在内存中的布局。

首先简单介绍一下Linux上C程序的内存分布。

一般情况下从低地址到高地址分布着:

  • 程序代码段及只读数据段

    • 程序代码,以及字符串常量等都存储在这里
  • 可读可写数据段
    • 全局变量,静态变量存储在这里  
  • 数据堆
    • 程序中动态分配的内存在这一块 
  • 共享库
    • 程序加载的共享库加载在这个地方
  • 数据栈
    • 局部变量,函数参数,函数返回地址等存储在这里
  • 内核地址
    • 操作系统内核内存空间

C语言代码如下(var_memory.c)

 #include <stdio.h>
#include <stdlib.h> #include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <argp.h> int global = ;
static int static_global = ; int main()
{
char *string_literal = "hello";
static int static_local = ;
char local_array[] = {'a', 'b', 'c'};
int *malloced = (int *)malloc(*sizeof(int)); pid_t pid = getpid();
printf("pid: %ld\n", (long)pid); // string_literaling literal and function in this module are loaded
// into readonly and excutable segment
printf("%.8p: addr of main\n", &main);
printf("%.8p: addr of local string_literal literal\n", string_literal);
puts(""); // static and global variables are allocated in data segment
printf("%.8p: addr of global int\n", &global);
printf("%.8p: addr of global static int\n", &static_global);
printf("%.8p: addr of local static int\n", &static_local);
puts(""); // allocated in heap
printf("%.8p: addr of local malloc\n", malloced);
puts(""); // errno is from a library
printf("%.8p: addr of errno\n", &errno);
puts(""); // printf is from a library
printf("%.8p: addr of printf\n", &printf);
puts(""); printf("%.8p: addr of argp_program_verstatic_localon_hook\n", & argp_program_version_hook);
puts(""); // local variable in stack
printf("%.8p: addr of local pid\n", &pid);
printf("%.8p: addr of local array\n", local_array);
puts(""); scanf("%s", local_array);
return ;
}

在linux命令行

make var_memory.c

生成var_memory可执行文件,运行

./var_memory

输出如下:

 pid:
0x004b26dd: addr of main
0x004b29d0: addr of local string_literal literal 0x004b4008: addr of global int
0x004b400c: addr of global static int
0x004b4010: addr of local static int 0x00d30160: addr of local malloc 0xb7db86b4: addr of errno 0xb7e0b1c0: addr of printf 0xb7f8f798: addr of argp_program_verstatic_localon_hook 0xbf85518c: addr of local pid
0xbf855198: addr of local array

打开另外一个终端窗口,根据上面输出的pid,运行命令

 pmap 4760 # 进程id以上面输出为准

输出如下

 :   ./var_memory
004b2000 4K r-x-- var_memory
004b3000 4K r---- var_memory
004b4000 4K rw--- var_memory
00d30000 136K rw--- [ anon ]
b7db8000 8K rw--- [ anon ]
b7dba000 1852K r-x-- libc-2.26.so
b7f89000 4K ----- libc-2.26.so
b7f8a000 8K r---- libc-2.26.so
b7f8c000 4K rw--- libc-2.26.so
b7f8d000 12K rw--- [ anon ]
b7fbb000 8K rw--- [ anon ]
b7fbd000 12K r---- [ anon ]
b7fc0000 8K r-x-- [ anon ]
b7fc2000 148K r-x-- ld-2.26.so
b7fe7000 4K r---- ld-2.26.so
b7fe8000 4K rw--- ld-2.26.so
bf836000 132K rw--- [ stack ]
total 2352K

从运行C语言程序与pmap的输出可以看出。

  • main函数(0x004b26dd)和字符串常量(0x004b29d0)被分配在了内存区域

    • 004b2000      4K r-x-- var_memory,其中rx代表只读和可执行
  • 全局变量global(0x004b4008),全局静态变量 static_global(0x004b400c),局部静态变量(0x004b4010)被分配在了数据段
    • 004b4000      4K rw--- var_memory,其中rw代表可以读和写
  • 使用malloc分配的空间(0x00d30160)处在堆(heap)里
    • 00d30000    136K rw---   [ anon ]
  • 共享库提供的全局变量errno(0xb7db86b4),被分配在共享库数据段里
    • b7db8000      8K rw---   [ anon ]
  • 共享库提供的函数printf(0xb7e0b1c0),被分配在共享库代码段里
    • b7dba000   1852K r-x-- libc-2.26.so
  • 局部变量(0xbf85518c,0xbf855198)被分配在了栈上
  • bf836000    132K rw---   [ stack ]

希望这篇文章,对同样困惑于C程序在内存中布局的你有所帮助。

Linux 上 C 程序的内存布局的更多相关文章

  1. Linux下C程序的内存布局

    参考下列书籍中的对应章节: <Linux高级程序设计(第3版)>第3章Linux进程存储管理.相关视频:一.二. <C专家编程>第6章 运动的诗章:运行时数据结构. <U ...

  2. Anatomy of a Program in Memory.剖析程序的内存布局

    原文标题:Anatomy of a Program in Memory 原文地址:http://duartes.org/gustavo/blog/ [注:本人水平有限,只好挑一些国外高手的精彩文章翻译 ...

  3. linux上应用程序的执行机制

    linux上应用程序的执行机制 执行文件是如何在shell中被"执行"的.本文中尽可能少用一些源码,免得太过于无 聊,主要讲清这个过程,感兴趣的同学可以去查看相应的源码了解更多的信 ...

  4. C语言程序的内存布局

    C语言程序的内存布局 一:C语言程序的存储区域 C语言编写的程序经过编绎-链接后,将形成一个统一的文件,它由几个部分组成,在程序运行时又会产生几个其他部分,各个部分代表了不同的存储区域: 1.代码段( ...

  5. Linux下C程序的内存映像

    2.Linux下C程序的内存映像 2.1. 代码段.只读数据段(1)对应着程序中的代码(函数),代码段在Linux中又叫文本段(.text)(2)只读数据段就是在程序运行期间只能读不能写的数据,con ...

  6. Linux上java程序的jar包启动通用脚本(稳定用过)

    Linux上java程序的jar包启动通用脚本如下: #! /bin/sh export LANG="zh_CN.GBK" SERVICE_NAME=` .sh` SCRIPT_N ...

  7. ESP32应用程序的内存布局

    应用程序内存布局 ESP32芯片具有灵活的内存映射功能.本节介绍ESP-IDF在默认情况下如何使用这些功能. ESP-IDF中的应用程序代码可以放置在以下内存区域之一中. IRAM(指令RAM) ES ...

  8. Linux 上如何清除 RAM 内存高速缓存,缓存和交换空间

    像任何其他的操作系统一样,GNU / Linux已经有效地实施了内存管理甚至更多.但是,如果有任何进程正在蚕食你的内存,你要清除它,Linux提供了一个方法来刷新或清除RAM缓存. 在Linux中如何 ...

  9. 分析Linux上的程序依赖

    ldd [path_to_exe] ldd通过调用动态链接器来获取可执行程序的依赖库,但是并不推荐在未知的可执行程序上执行业ldd来获取其依赖库,因为部分版本的ldd会直接通过调用该可执行程序来获取其 ...

随机推荐

  1. MySQL Command Line Client显示中文的部分为空

    一连接数据库的时候就设置如下: 先设置 set names gbk,然后再插入就显示中文

  2. jsoup爬取图片到本地

    因为项目需求,需要车辆品牌信息和车系信息,昨天用一天时间研究了jsoup爬取网站信息.项目是用maven+spring+springmvc+mybatis写的. jsoup开发指南地址:http:// ...

  3. X86寄存器全称

    body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...

  4. Linux修改主机名脚本-不重启-支持RedHat、SUSE

    需要用脚本修改主机名,涉及RedHat.SUSE系统,并且要求修改立即生效且不重启,下面就是我的脚本. 使用脚本的方法如下: 1 首先创建一个脚本文件,假如命名为ModifyHostname.sh: ...

  5. junit源码解析--测试驱动运行阶段

    前面的博客里面我们已经整理了junit的初始化阶段,接下来就是junit的测试驱动运行阶段,也就是运行所有的testXXX方法.OK,现在我们开始吧. 前面初始化junit之后,开始执行doRun方法 ...

  6. freemarker中值比较的写法

    因为freemarker中不能使用<.>的方式进行值的比较,大于使用gt,小于使用lt.所以集合大于0判断,如下: <#if students?size gt 0><#i ...

  7. 用powershell实现:“倩女幽魂姥姥”版《语音报警系统》

    ------[第一章 前言]------ win7,及以上版本中,是自带语音库的,系统自带一套女声中文库,一套女声英文库.用powershell调用,从而发音,制作报警系统.是一件太简单的事情,只需要 ...

  8. shell第二篇

    第二篇知道shell是什么,再来了解一下shell的分类及相关历史 参考百度百科:shell 1.shell概念(计算机壳层) 在计算机科学中,Shell俗称壳(用来区别于核),是指"提供使 ...

  9. Percona监控MySQL模板详解

    InnoDB Adaptive Hash Index 显示了"自适应哈希索引"的使用情况,哈希索引只能用来搜索等值的查询. # Hash table size 17700827, ...

  10. sqlserver数据库使用技巧(一)--限制数据库的大小

    如何限制数据库的大小? 随着数据库的使用,他占用的空间会越来越大,为了便于资源的合理分配和管理,我们可以限制其最大的大小,这个建议只在测试环境使用 具体操作如下: 打开sqlserver数据库管理工具 ...