`
haoningabc
  • 浏览: 1482208 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

(转)从C源程序到Linux可执行程序之旅的四个阶段

    博客分类:
  • c
 
阅读更多
转载http://article.yeeyan.org/view/20180/222590
 编写一个C程序,使用gcc进行编译,然后得到一个可执行程序。相当简单,对吗?

你有没有问过自己,编译处理期间到底发生了什么事情,还有C程序如何转换成可执行程序呢?

为了最后变成可执行程序,源代码要经过的行程主要有四个阶段。

C源程序变成可执行程序的四个阶段如下:

预处理编译汇编链接
本文第一部分将讨论gcc编译器在把C程序的源代码编译成可执行程序时所经历的步骤。

在进一步讨论之前,让我们先用hello world这个简单的例子,迅速地看一下如何使用gcc编译和运行C代码。

$ vi print.c

#include

#define STRING "Hello World"

int main(void)

{

/* Using a macro to print 'HelloWorld'*/

printf(STRING);

return 0;

}

现在,让我们运行gcc编译器编译这个源代码以建立可执行程序。

$ gcc -Wall print.c -o print

在上述命令中:

gcc – 调用GNU C编译器-Wall – gcc打开所有警告提示的标记。-W表示警告,我们还把“all”传给-W。print.c – 输入给gcc的C程序-o print – 指示C编译器创建名为print的可执行程序。如果没有指定-o,C编译器将默认地创建名为a.out的可执行程序
最后,执行print,这个命令将执行C程序并显示hello world。

$ ./printHello World
注意:当你致力于编写包含几个C程序的大项目时,请按我们早先讨论的一样,用make实用程序来操控C程序的编译。

现在,我们对如何使用gcc把源代码转换成二进制代码已经有了基本的概念,我们将重新考虑把C源程序变成可执行程序必须经历的四个阶段。

1. 预处理
这正是源代码必经的第一阶段。这个阶段要完成以下任务:

宏替换去掉注释包含文件的扩充
为了更好地理解预处理,可用标记-E编译上面的程序print.c,这将把预处理后的结果显示在标准输出上。

$ gcc -Wall -E print.c
如下所示,使用标记-save-temps可能会更好。标记-save-temps指示编译器把被gcc使用的临时中间文件存储在当前目录中。

$ gcc -Wall -save-temps print.c -o print
所以,当我们用-save-temps标记来编译程序print.c时,就可以在当前目录中(除可执行文件print以外,还可以)得到以下中间文件:

$ lsprint.iprint.sprint.o
预处理的结果存储在扩展名.i的临时文件(本例即print.i)中。

$ vi print.i........................# 846 "/usr/include/stdio.h" 3 4extern FILE *popen (__const char *__command, __const char *__modes) ;extern int pclose (FILE *__stream);extern char *ctermid (char *__s) __attribute__ ((__nothrow__)); # 886 "/usr/include/stdio.h" 3 4extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__)); # 916 "/usr/include/stdio.h" 3 4# 2 "print.c" 2 int main(void){printf("Hello World");return 0;}
在以上输出结果中,可见源代码现在充满许许多多信息,但在结尾部分仍然能够看到我们编写的几行代码。让我们先分析这些代码行。

首先观察到的是printf()的参数现在直接包含字符串“Hello World”而不是宏。事实上,宏的定义和宏的使用已经完全消失。这证实,预处理阶段的第一个任务就是展开所有的宏。其次看到的是,我们在原来的代码中所写的注释也不在了。这表明去掉了所有的注释。第三,找不到#include这一行了,取而代之的是完整的大量代码。所以,可以放心地断定,头文件stdio.h已经展开,并且逐字逐句地包含在我们的源代码中。因此,我们知道,编译器就能够看见printf()函数的声明。
我在搜索print.i文件时发现,函数printf声明为:

extern int printf (__const char *__restrict __format, ...);
关键字extern表明,函数printf()不是在这个文件中定义的,它是外部函数。稍后就会明白,gcc如何获得printf()的定义。

可用gdb调试C程序。既然对预处理阶段会发生什么有了相当好的理解,那么,让我们进入下一阶段吧。

2. 编译
编译器完成预处理阶段的工作之后,下一步就是把print.i作为输入,进行编译,然后产生编译过的中间输出结果。这一阶段的输出文件是print.s。print.s中包含的是汇编级指令。

用编辑器打开文件print.s并查看文件内容。

$ vi print.s.file "print.c".section .rodata.LC0:.string "Hello World".text.globl main.type main, @functionmain:.LFB0:.cfi_startprocpushq %rbp.cfi_def_cfa_offset 16movq %rsp, %rbp.cfi_offset 6, -16.cfi_def_cfa_register 6movl $.LC0, %eaxmovq %rax, %rdimovl $0, %eaxcall printfmovl $0, %eaxleaveret.cfi_endproc.LFE0:.size main, .-main.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3".section .note.GNU-stack,"",@progbits
虽然我不想在汇编级程序设计方面卷得太深,但迅速查看后可以断定,这一汇编输出结果采用一些汇编程序能够理解的指令格式并把它转换成机器语言。

3. 汇编
在这一阶段,文件print.s被当作输入并产生中间文件print.o。这个文件也被认为是目标文件。

这个文件由汇编程序产生,汇编程序理解并把带有汇编指令的.s文件转换成包含机器指令的目标文件(.o)。这一阶段只把现有的代码转换成机器语言,而像printf()这样的函数调用暂不解析。

由于这个阶段的输出是机器级文件(print.o),所以不能查看其内容。如果你还是试图要打开并查看print.o,那么,你将看到的是一些完全不可读的东西。

$ vi print.o^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@0^^@UH<89>å¸^@^@^@^@H<89>ǸHello World^@^@GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3^@^T^@^@^@^@^@^@^@^AzR^@^Ax^P^A^[^L^G^H<90>^A^@^@^\^@^@]^@^@^@^@A^N^PC<86>^B^M^F^@^@^@^@^@^@^@^@.symtab^@.strtab^@.shstrtab^@.rela.text^@.data^@.bss^@.rodata^@.comment^@.note.GNU-stack^@.rela.eh_frame^@^@^@^@^@^@^@^@^@^@^@^......…
通过查看文件print.o,唯一能够解释的是有关字符串ELF。

ELF表示可执行并且可链接的格式。

相对而言,这是由gcc产生的机器级目标文件和可执行文件的新格式。在此之前,使用的是称之为a,out的格式。据说,ELF的格式比a,out的复杂得多。

注意:如果没有指定输出文件名来编译代码,虽然所产生的输出文件的名字是a.out,但现在已经改为ELF格式。仅仅是缺省的可执行文件名字依旧一样。

4. 链接
这是最后的阶段,完成对所有函数调用及其定义的链接。正如早先讨论的一样,直到这时,gcc还不了解像printf()一样的库函数的定义。在编译器确切知道所有这些函数在什么地方实现之前,函数调用只是简单地采用占位符。正是在这一阶段,print()的定义得到解析,并且插入printf()函数的实际地址。

链接器在这个阶段加入行动并完成这项任务。

链接器也做一些额外的工作;它把一些程序开始运行和程序结束运行所需的附加代码合并到程序。例如,设置运行环境的标准代码,像传递命令行参数、环境变量给每个程序等。类似地,把程序的返回值返回给系统也需要一些标准代码。

编译器的上述任务可以通过小实验加以证实。从现在开始,我们就知道链接器把.o文件(print.o)转换成可执行文件(print)。

因此,要是比较print.o和print这两个文件的大小,就会发现差别。

$ size print.o   text    data     bss     dec     hex filename     97       0       0      97      61 print.o  $ size print   text    data     bss     dec     hex filename   1181     520      16    1717     6b5 print
通过size命令,我们获得一个粗略的概念,从目标文件到可执行文件,输出文件的大小增加了。这全都是因为链接器把额外的标准代码和我们的程序合并在一起。

现在,你知道C源程序在变成可执行程序之前发生的事情,了解到预处理、编译、汇编和链接等阶段。链接阶段还有更多事情需要了解,我们将在这一系列的下一篇文章报道。

分享到:
评论

相关推荐

    Linux程序设计第四版及源代码英文原版(附加勘误表)

    《Linux程序设计第四版》是Linux系统编程领域的一本经典著作,它为初学者和有经验的程序员提供了全面深入的Linux编程知识。本书涵盖了Linux环境下应用程序开发的各种关键方面,包括进程管理、文件I/O、系统调用、...

    嵌入式Linux相关_GCC精彩之旅

    4. **链接**(Linking):最后一步是链接,GCC会将多个目标文件链接起来,并解决符号引用问题,最终生成可执行文件。 #### 实战案例:Hello World程序 下面通过一个简单的“Hello World”示例来具体演示GCC的工作...

    linux 用GDB调试C和C++程序

    首先编译生成可执行文件 `tst`: ```bash gcc -g -Wall tst.c -o tst ``` ##### 使用GDB调试程序 1. **启动GDB**: ```bash gdb tst ``` 启动后会显示GDB版本信息及版权声明。 2. **列出源代码**: 使用 `l`...

    Linux入门之路

    本篇将围绕Linux的基础知识,包括安装、基本操作、文件系统、权限管理、进程控制、网络配置等关键点进行深入讲解,帮助你开启Linux探索之旅。 一、Linux安装 Linux有多种发行版,如Ubuntu、CentOS、Fedora等。新手...

    GCC精彩之旅——gcc

    在使用GCC进行程序开发时,编译过程通常分为四个阶段: 1. **预处理(Pre-Processing)**:GCC会调用预处理器cpp,处理宏定义(如#define)、条件编译指令(#if/#else/#endif)以及包含的头文件(#include)。预处理的...

    linux编程入门!!!!!

    2. 使用GCC编译源代码:`gcc -o hello hello.c`,生成可执行文件`hello`。 3. 运行程序:`./hello`,在终端中查看输出结果。 六、调试与性能分析 1. 调试工具:GDB(GNU Debugger)是Linux下强大的调试工具,可以...

    老罗的Android之旅导读PPT.rar

    《老罗的Android之旅》是一份深入探讨Android操作系统核心概念的教学资料,主要涵盖了Android应用程序的各个方面,包括UI架构、组件设计、虚拟机、消息处理、硬件抽象层、安全防护、输入事件处理、资源管理和进程...

    linux入门指南学习linux的

    Linux入门指南旨在为初学者提供一个清晰且逐步的指引,帮助他们熟悉Linux操作系统的基本概念、命令行界面以及常用工具的使用。Linux是一种开源的操作系统,它以其稳定性和...希望这份指南能帮助你开启Linux的学习之旅。

    ReLinuxFromZero

    - `$ gcc -o say_hello say_hello.c`:编译程序,并将结果输出到名为`say_hello`的可执行文件中。 - `$ ./say_hello`:运行编译后的程序。 - **用户管理**: - `$ sudo su`:提升权限成为root用户。 - `# ...

    coreutils-7.5 ( linux 工具源码)

    《深入解析coreutils-7.5:Linux工具的源码之旅》 在Linux操作系统中,coreutils是一个不可或缺的组件,它包含了我们日常使用的一系列基础命令的源代码,如`cp`、`sort`、`echo`和`ls`等。这些命令在我们的日常工作...

    linux入门linux入门linux入门linux入门

    以上就是Linux入门的基本内容,通过学习和实践,你可以逐渐掌握这个强大且灵活的操作系统,开启你的Linux之旅。在学习过程中,不要忘记利用在线资源,如Linux论坛、文档和教程,它们将是你解决问题的好帮手。

    qt编写的界面程序打包

    希望本文能对您有所帮助,使您的Qt程序开发之旅更加顺畅。 #### 五、参考资料 - [Qt程序打包详解](https://blog.csdn.net/clinuxf/article/details/79657085) - [Qt软件及文档下载地址]...

    Linux 学习基础 初学者参考

    在IT领域,Linux操作系统是开发者、系统管理员以及技术爱好者的重要工具。它以其开源、免费、稳定和可定制性而受到广泛赞誉。对于初学者来说,掌握Linux基础...希望这份指南能为你的Linux学习之旅提供一个良好的开端。

    Linux 基础应用

    Linus Torvalds正是通过学习Minix开始了自己的操作系统开发之旅。 - **GNU计划**:Richard M. Stallman在1984年发起的GNU项目旨在创建一套完全自由的类UNIX操作系统。 - **POSIX标准**:这一系列标准由IEEE制定,为...

    linux常用命令大全.doc

    - **示例**:`chmod 755 file.txt` 设置文件 `file.txt` 的权限为可读、可写、可执行。 2. **chown** - 改变文件或目录的所有者和所属组 - **用途**:更改文件或目录的所有者和组。 - **示例**:`chown user:...

    Linux详细,CentOS,Ubuntu都适用.docx

    无论是使用CentOS还是Ubuntu,以上的知识都能为你的Linux学习之旅打下坚实的基础。通过对shell的深入了解,建立良好的编程习惯,以及熟悉变量的定义和操作,可以大大提高你在Linux环境下的工作效率和代码质量。而...

    Linux操作系统的安装使用实验报告.zip

    **Linux操作系统安装与使用实验报告** 在信息技术领域,Linux操作系统是一个不可或缺的重要组成部分,它以其开源、免费、稳定和高效的特点,被广泛应用于服务器...希望这份实验报告能为你的Linux之旅提供有力的支持。

    Ubuntu Linux轻松入门ppt

    **Ubuntu Linux轻松入门** Ubuntu Linux是一款基于Debian的开源操作系统,以其用户友好、社区支持和免费的特点在全球范围内广受欢迎。本教程将引导你逐步踏入Ubuntu Linux的...欢迎下载并开始你的Ubuntu Linux之旅!

    深入解析:使用EXPLAIN优化MySQL查询之旅

    ### 深入解析:使用 EXPLAIN 优化 MySQL 查询之旅 #### 一、MySQL简介与特点 MySQL作为一款流行的开源关系型数据库管理系统(RDBMS),因其强大的功能与灵活性,在Web应用程序开发领域占据着举足轻重的地位。它不仅...

Global site tag (gtag.js) - Google Analytics