简介
在《UNIX环境高级编程3》的7.6节有如下一图,描述了对c程序运行的内存分布,但我一直有些疑惑的地方,比如全局的char *ptr = "abc"一共消耗多少内存?之前有些不确定对错的猜想,于是写了几行代码验证一下,加深对程序的理解。这篇文章记录了自己的验证过程和验证结果,验证过程主要借助readelf、objdump这两个工具,验证结果在代码的注释中。下图的initialized data段需要细分为只读区和可读写区。
测试程序及结果
代码以不同的方式使用字符串,每行代码的注释中说明测试的结论。后面列出了代码在Cortex-A7的运行结果,以及对可执行文件分析的信息:
/****test-layout.c************/
#include <stdio.h>
char *g_ptr = "aaa";/* 指针g_ptr及字符串全部占用initialized data段的rw区 */
const char *g_c_ptr = "bbb";/* 指针g_c_ptr占用rw区,后面的字符串占用ro区*/
char g_buf[] = "ccc"; /* 只有字符串占用rw区 */
const char g_c_buf[] = "ddd"; /* 只有字符串占用ro区 */
char g_uninit_buf[16]; /* 在程序执行时占用uninitialized data区 */
int main(int argc, char **argv)
{
printf("main: %p \n", &main); /*main函数处于text段*/
printf("globle:\n");
printf(" g_ptr: %p\n", g_ptr);
printf(" &g_ptr: %p\n", &g_ptr);
printf(" g_c_ptr: %p\n", g_c_ptr);
printf("&g_c_ptr: %p\n", &g_c_buf);
printf(" g_buf: %p\n", g_buf);
printf(" g_c_buf: %p\n", g_c_buf);
printf("local:\n");
char *l_ptr = "eeee"; /*字符串位于rw段中,指针占用栈空间*/
printf(" l_ptr: %p\n", l_ptr );
const char *l_c_ptr = "ffff";/*字符串位于ro段中,指针占用栈空间*/
printf(" l_c_ptr: %p\n", l_c_ptr);
static const char *l_s_c_ptr = "gggg";/*指针占用rw空间,字符串占用ro空间*/
printf("&l_s_c_ptr: %p\n", &l_s_c_ptr);
printf(" l_s_c_ptr: %p\n", l_s_c_ptr);
char l_buf[] = "hhhh";/*如果这里3个h及以下,h将会在text段记录,如果4和及以上,h将会存在于ro段。程序运行时在栈上分配数组,然后将h复制到这块栈空间来*/
printf(" l_buf: %p\n", l_buf );
const char l_c_buf[] = "iiii";/*如果这里3个i及以下,i将会在text段记录,如果4和及以上,i将会存在于ro段。运行时在栈上分配数组,并将i复制到该地址,这里的const只在编译阶段有用,运行阶段其内容可以改*/
printf(" l_c_buf: %p\n", l_c_buf );
static char l_s_buf[] = "jjjj"; /*只在rw区分配空间保存此字符串*/w
printf(" l_s_buf: %p\n", l_s_buf);
static const char l_s_c_buf[] = "kkkk"; /*只在ro区分配空间保存此字符串*/
printf(" l_s_c_buf: %p\n", l_s_c_buf);
printf("\n");
return 0;
}
编译及运行信息
使用命令arm-linux-gnueabi-gcc test-layout.c编译后运行结果中,能够看到那些数据存储地址大体分布在0x107xx,和0x210xx,以及0xbeaf0cxx三部分中。这三段分别对应ro段,rw段和栈空间,从后面的elf文件的展示中能更清晰的看到程序段的边界。
readelf查看分段信息
使用命令readelf -S a.out展示可执行文件a.out的每一段的运行时空间范围,其中data段为rw区,运行时地址从0x2102c开始,大小为0x1b;rodata段为ro区,从0x10754开始,大小为0x139,这段相对较大,因为存储了程序中的大部分数据。选项-S (Display the sections’ header )。
objdump查看ro区和rw区数据
使用命令 arm-linux-gnueabi-objdump -s a.out可打印出可执行文件的每一段的详细内容,以hex格式显示。在rodata段和data段能够找到代码中所用的从aaa一直到kkk的所有字符串。选项-s (Display the full contents of all sections requested)。
elf分段标准
gcc生成的可执行程序为elf格式,,在ubuntu的/usr/include/elf.h中存在描述elf的数据类型,博客elf文件格式总结对这种有详细介绍。
参考
elf文件格式总结
https://blog.csdn.net/flydream0/article/details/8719036
Memory Layout of C Programs
https://www.jianshu.com/p/863b279c941e
使用readelf和objdump解析目标文件
https://www.geeksforgeeks.org/memory-layout-of-c-program/