C程序运行的背后

分享到:

一个成功的男人背后,至少有一个伟大的女人;一个不成功的男人,至少有一双手。

而一个C程序,无论成功不成功,它的背后一定有一个操作系统,一个shell,一套工具链。

世界本就不公平。隐藏在显而易见的事实背后的,你若能看透,便可以站在对自己公平的那一端。

1、进程地址空间

一个进程一旦建立,就会自认为占有4G内存(X86_32),这个内存被称作虚拟内存,也就是进程的地址空间。在Linux下,进程地址空间的布局大致如下图所示,其中的用户空间大致由这些部分组成:

  1. 代码段
  2. 初始化数据段
  3. 未初始化数据段

C程序运行的背后

这些段,反映到ELF格式的目标文件(object file)中,就又可能由许多不同的节(section)组成。节这个东西更加细致复杂,暂且不表。

代码段

保存的是可执行指令,通常是只读的,防止指令被程序自身修改。但程序是无法防止被人为修改,否则哪来那么多的修改器。Vim就可以直接编辑二进制文件,指令的机器码任意修改。

存储实例:

push  %ebp

movl  %esp, %ebp

初始化数据段

保存的是已初始化了的全局变量和静态变量,它可以进一步划分为只读区域和可读写区域。

存储实例:

Char *string = “hello world”(全局)

“hello world”在只读区域,指针string在可读写区域

而Char string[] = “hello world”(全局)

就只存储string在读写区域中。因为string已被分配存储空间。

Static int class = 6 (全局/局部)

全局的容易理解。局部静态变量的意义,在于函数调用完后,其占用的存储单元也不被释放。如此便不可以存放到栈中,而又已被初始化,那么存放到这个段自然是合理的。

未初始化数据段

通常称为bss段,名字来自于“block started by symbol”—由符号开始的块。存放于此段的变量,在程序执行之前就被初始化为0或Null指针。

注意,未赋值的指针会被初始化为空指针!如果程序中定义的指针没有初始化,而后面又赋值于它,那么在Linux下会引发“段错误”。

这就是个狗皮膏药,用处大,却难搞。函数调用时,对栈的操作基本上由编译器完成。函数一旦被调用,就会生成一个栈帧(stack frame),栈帧的范围由两个 “栈指针”寄存器%ebp、%esp限定。

存储实例:

Caller的返回地址;

Caller的寄存器信息,如%ebp,%eax;

Callee自身的局部变量

用户手动分配内存的区域,malloc和free,谁用谁知道。另外,共享库和动态加载的模块,也存放于堆中。

那么问题来了,实际编译好的目标文件是否真的是这样的呢?

以一个非常简单的C程序—memlayout.c—作为例程:

int main()  {
return 0;
}

用GCC分别编译生成memlayout.o和memlayout文件,并查看它们的内存布局:

[root@localhost ~]# size memlayout.o
text
data
bss
dec
hex
filename
66
0
0
66
42
memlayout.o
[root@localhost ~]# size memlayout
text
data
bss
dec
hex
filename
1055
272
4
1331
533
memlayout

这个程序没有定义任何的变量,由memlayout.o可以看出,data、bss为0是符合预期的。

段依然还是那些段,可最终的可执行文件如何却把它们都搞大了?

我并没有调用exit,为何程序自动流产?

男人的直觉也很准的,特别是程序出轨的时候。凭男人的直觉,我想,一定是编译器(实质是链接器)在某个地方插了一脚。

这也是一个细琐的问题,先做简要说明,容以后再表。

2、程序的生命周期

编译好的C程序是躺在磁盘里的,这时只能叫文件。加载到内存并撒腿狂奔的时候,才叫进程。老师们也告诉过我们,一个运行的“hello world”也是一个进程。所以一定要先有一个进程环境,程序才有狂奔的空间。我的家里没有草原,所以董小姐没有理我。

一个C程序的前世今生大概是这样的:

  • Shell首先创建一个子进程,设置好进程环境;
  • 子进程调用execve而陷入内核;
  • 内核调用加载器程序,加载器清理子进程环境后,再加载可执行文件到子进程环境中;
  • 加载器跳转到该程序的入口点(entry point),开始执行C启动代码;
  • 调用main函数,执行真正的C程序;
  • 调用_exit,把控制交还给内核。

C程序运行的背后

也就是说,在写好的main函数之前,编译器添加了一段C启动代码,是C程序执行之前的准备工作;在main函数之后,编译器至少添加(调用)了 _exit()来保证进程的正确终止。这也是为什么,中间目标文件和最终可执行文件size相差悬殊,用户空间的程序总会终结的原因。 

来源:chenwu128的博客

昵    称:
验证码:

相关文档:

  • 谷歌URL解析和规范化C库:google-url
    google-url 是一个用来解析URL和对URL进行标准化的C语言库。...
  • C++加密/解密库:libsodium
    libsodium 是一个流行、易于使用的软件库。主要用于加密、解密、签名和生成密码哈希等等。这是一个可移植的、跨编译器支持、可安装...
  • C++ Virtual详解
    Virtual是C++ OO机制中很重要的一个关键字。只要是学过C++的人都知道在类Base中加了Virtual关键字的函数就是虚拟函数(例如函数print),于...
  • C语言常用工具包 libscl
    libscl (SCL) 是一个C语言常用工具包,主要功能包括哈希表、列表、队列、堆栈、符号、平衡二叉树、向量等数据结构的实现。...
  • C++ typename的起源与用法
    侯捷在Effective C++的中文版译序中提到: C++的难学,还在于它提供了四种不同(但相辅相成)的程序设计思维模式:procedural-based, object-b...
  • Java 中嵌入c/c++
    Java Native Interface(JNI)是Java语言的本地编程接口,是J2SDK的一部分。在java程序中,我们可以通过JNI实现一些用java语言不便实现的功能。...
  • 通用的 C/C++ 库 qLibc
    qLibc 项目的目的就是提供一个通用的 C/C++ 库,包括所有种类的容器和常用工具函数。...
  • C程序运行时内存结构分析
    静态变量存储在静态存储区,局部变量存储在动态存储区(栈),代码存放在代码区 寄存器,EBP指向栈底,ESP指向栈顶,EIP指向正在执...
  • C++ 解析器 Cling
    Cling 是一个交互式的 C++ 解析器,基于 LLVM 和 C++ 的前端 clang。可用于替换当前 C++ 解释器 CINT,比 CINT 强...
  • C++用户界面设计器 Glade
    Glade是RAD (快速应用开发)工具,用于创建基于GTK 工具包和GNOME桌面。...
  • 跨平台C++库 CrissCross
    CrissCross是一种小型的跨平台C++库,用于处理控制台和文件I / O , CPU的识别( CPUID ) ,散列( MD2 , MD4 , MD5编码,了SHA - 1 ,SHA- 256 ,S...
  • C++标准库实现 libc++
    libc++是一个C++标准库实现,突出了标准符合和高效的代码生成。如果C++'0x标准得以批准,libc++也将提供支持。libc++由 Howard Hinnant开发...
  • 在C++中HTTP库速查表
    不幸的是,标准C+ +库没有提供了与HTTP协议相关的工具。以下是一些C++ HTTP库,可以让我们运行REST服务,解析网页或写一个简单的机器人...
  • 12个有趣的C语言问答
    12个有趣的C语言问答...
  • 纯C语言INI文件解析
    在一个跨平台( Android 、Windows、Linux )项目中配置文件用 INI 格式,自己写了个解析库,纯C语言的,简单好用。 可以解析 INI 格式...
  • C++图像处理库 ImageStone
    ImageStone是一套功能强大的C++图像处理库,它可以在多个平台之间移植。...
  • 揭秘Facebook官方底层C++函数 - Folly
    Facebook近日公布了其官方底层C++函数Folly,Folly(该缩略语表示Facebook开源代码库)其实是C++11组件库,这些组件在设计时着眼于实用性和...
  • C/C++代码静态分析插件:VisualStudio_Scan
    VisualStudio_Scan 是一款开源免费,集成在 Visual Studio 中的 c/c++ 代码静态分析插件,集成了cppcheck,coverity,pclint 等业界优秀的静态分析工具...
  • C编程语言的编译器:8cc C Compiler
    8CC是一个C编程语言的编译器。它的目的是支持所有C11语言功能,同时尽可能保持代码少而简单。...
  • C编译器 c++编译器 wieldylcc
    用c++重构了lcc源代码,去掉了lcc中支持多种处理器的模式,仅支持x86一种。项目目标是编译器的源代码是易于理解的,编译出的asm文件也...