C++编译运行过程分析

为了减轻使用机器语言编程的痛苦,人们进行了一种有益的改进:用一些简洁的英文字母、符号串来替代一个特定的指令的二进制串,比如,用“A D D”代表加法,“M O V”代表数据传递等等,这样一来,人们很容易读懂并理解程序在干什么,纠错及维护都变得方便了,这种程序设计语言就称为汇编语言,即第二代计算机语言。然而计算机是不认识这些符号的,这就需要一个专门的程序,专门负责将这些符号翻译成二进制数的机器语言,这种翻译程序被称为汇编程序。因为汇编指令和机器语言之间有着一一对应的关系,这可比英译汉或汉译英简单多了。

      高级语言是偏向人,按照人的思维方式设计的,机器对这些可是莫名奇妙,不知所谓。鱼与熊掌的故事在计算机语言中发生了。于是必须要有一个桥梁来衔接两者,造桥可不是一件简单的事情。当你越想方便,那桥就得越复杂。那高级语言是如何变成机器语言的呢,这个过程让我慢慢道来。

       编译将源代码转换为机器可认识代码的过程。编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。

C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件 

1.编译预处理   

xxx.cpp-->xxx.i

读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。 

伪指令主要包括以下四个方面

(1)宏定义指令

如# define Name TokenString,#undef等。对于前一个伪指令,预编译所要作得的是将程序中的所有Name用TokenString替换,但作为字符串常量的Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。 

(2)条件编译指令

如#ifdef,#ifndef,#else,#elif,#endif,等等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。 

(3)头文件包含指令

如#include "FileName"或者#include <FileName>等。在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。 

包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(<>)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。

(4)特殊符号

预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。

2.编译阶段

xxx.i-->xxx.s

(1)编译

经过预编译得到的输出文件中,将只有常量。如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,},+,-,*,\,等等。预编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。 

(2)优化阶段

优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。上图中,我们将优化阶段放在编译程序的后面,这是一种比较笼统的表示。

对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。

后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。 

经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。 

4.汇编过程

xxx.s-->xxx.obj

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

 目标文件由段组成。通常一个目标文件中至少有两个段:

代码段  该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。  

数据段  主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

UNIX环境下主要有三种类型的目标文件: 

(1)可重定位文件  其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

 (2)共享的目标文件  这种文件存放了适合于在两种上下文里链接的代码和数据。第一种事链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。

 (3)可执行文件  它包含了一个可以被操作系统创建一个进程来执行之的文件。

汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

5.链接程序

xxx.obj-->xxx.exe

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种: 

(1)静态链接  在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。

 (2)动态链接  在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。 

     对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

  经过上述五个过程,C源程序就最终被转换成可执行文件了

如下为具体案例:

 

C/C++程序编译步骤详解

C/C++语言很多人都比较熟悉,这基本上是每位大学生必学的一门编程语言,通常还都是作为程序设计入门语言学的,并且课程大多安排在大一。刚上大学,孩子们还都很乖,学习也比较认真,用心。所以,C/C++语言掌握地也都不错,不用说编译程序,就是写个上几百行的程序都不在话下,但是他们真的知道C/C++程序编译的步骤么?

我想很多人都不甚清楚,如果他接下来学过“编译原理”,也许能说个大概。VC的“舒适”开发环境屏蔽了很多编译的细节,这无疑降低了初学者的入门门槛,但是也“剥夺”了他们“知其所以然”的权利,致使很多东西只能死记硬背,遇到相关问题就“丈二”。实际上,我也是在学习Linux环境下编程的过程中才逐渐弄清楚C/C++源代码是如何一步步变成可执行文件的。

总体来说,C/C++源代码要经过:预处理、编译、汇编和链接四步才能变成相应平台下的可执行文件。大多数时候,程序员通过一个命令就能完成上述四个步骤。比如下面这段C的“Hello world!”代码:

File: hw.c

#include stdio.h>

int main(int argc, char *argv[])

{

        printf("Hello World!\n");

        return 0;

}

如果用gcc编译,只需要一个命令就可以生成可执行文件hw:

 

 

首先是预处理,预处理后的文件hw.i:

# 1 "hw.c"

# 1 ""

# 1 ""

...

__extension__ typedef __quad_t __off64_t;

__extension__ typedef int __pid_t;

__extension__ typedef struct { int __val[2]; } __fsid_t;

...

extern int remove (__const char *__filename) __attribute__ ((__nothrow__));

extern int rename (__const char *__old, __const char *__new) __attribute__ ((__nothrow__));

...

int main(int argc, char *argv[])

{

printf("Hello World!\n");

return 0;

}

注:由于文件比较大,所以只留下了少部分具有代表性的内容。

可以看见预处理器把所有要包含(include)的文件(包括递归包含的文件)的内容都添加到了原始的C源文件中,然后把其输出到输出文件,除此之外,它还展开了所有的宏定义,所以在预处理器的输出文件中你将找不到任何宏。这也提供了一个查看宏展开结果的简便方法。

第二步“编译”,就是把C/C++代码“翻译”成汇编代码:

.file "hw.c"

        .section .rodata

.LC0:

        .string "Hello World!\n"

        .text

.globl main

        .type main, @function

main:

        pushl %ebp

        movl %esp, %ebp

        subl $8, %esp

        andl $-16, %esp

        movl $0, %eax

        addl $15, %eax

        addl $15, %eax

        shrl $4, %eax

        sall $4, %eax

        subl %eax, %esp

        subl $12, %esp

        pushl $.LC0

        call printf

        addl $16, %esp

        movl $0, %eax

        leave

        ret

        .size main, .-main

        .section .note.GNU-stack,"",@progbits

        .ident "GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)"

这个汇编文件比预处理后的C/C++文件小了很多,去除了很多不必要的东西,比如说没用到的类型声明和函数声明等。

第三步“汇编”,将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux上一般表现为ELF目标文件。

xiaosuo@gentux hw $ file hw.o

hw.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

最后一步“链接”,将上步生成的目标文件和系统库的目标文件和库文件连接起来,最终生成了可以在特定平台运行的可执行文件。为什么还要连接系统库中的某些目标文件(crt1.o, crti.o等)呢?这些目标文件都是用来初始化或者回收C运行时环境的,比如说堆内存分配上下文环境的初始化等,实际上crt也正是C RunTime的缩写。这也暗示了另外一点:程序并不是从main函数开始执行的,而是从crt中的某个入口开始的,在Linux上此入口是_start。以上Makefile生成的是动态连接的可执行文件,如果要生成静态连接的可执行文件需要将Makefile中的相应段修改:

hw: hw.o

    ld -m elf_i386 -static -o hw /usr/lib/crt1.o \

        /usr/lib/crti.o \

        /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtbeginT.o \

        -L/usr/lib/gcc/i686-pc-linux-gnu/3.4.6 \

        -L/usr/i686-pc-linux-gnu/lib \

        -L/usr/lib/ \

        hw.o --start-group -lgcc -lgcc_eh -lc --end-group \

        /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtend.o \

        /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../crtn.o

至此,一个可执行文件才最终创建完成。通常的项目中并不需要把编译过程分得如此之细,前三步一般是合为一体的,在Makefile中表现如下:

hw.o: hw.c

    gcc -o hw.o -c hw.c

实际上,如果对hw.c进行了什么更改,那么前三步大多数情况下都是不可避免的。所以把他们写在一起也并没有什么坏处,相反倒可以用--pipe参数告诉编译器用管道替代临时文件,从而提升编译的效率。

转载自https://www.cnblogs.com/SarahLiu/p/5954824.html 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/567980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql给用户授权最大_mysql 给用户授权

MySQL用户授权 GRANT 语句的语法如下&#xff1a; GRANT privileges (columns) ON what TO user IDENTIFIEDBY "password" WITH GRANT OPTION 对用户授权 mysql>grant rights on database.* to userhost identified by &qu…

python插入排序_直接插入排序(python实现)

这篇博文用来介绍直接插入排序直接插入排序基本思想&#xff1a;每次将一个待排序的记录插入到已经排好序的数据区中&#xff0c;直到全部插入完为止直接插入排序算法思路&#xff1a;在直接插入排序中&#xff0c;数据元素分为了有序区和无序区两个部分&#xff0c;在这里我们…

STL1-函数模板

1、函数模板和普通函数区别 //普通函数可以进行自动类型转换&#xff0c; //函数模板必须精确类型匹配; //函数模板可以被重载;c优先考虑普通函数;#include<iostream> using namespace std; //函数模板-->产生模板函数-->调用函数 template<class T> T MyAd…

ruby 安装mysql_ruby安装mysql

ruby安装mysqlruby on rails安装mysql数据库1. 下载mysql软件http://www.mysql.cn/mysql中文官方网站下载安装软件&#xff0c;选择5.02. 安装mysql&#xff0c;设置默认字符集为utf-83. 下载mysql for ruby的驱动并安装http://rubyforge.org/搜索mysql&#xff0c;找到mysql -…

jupyter安装与迁移文件

1、安装 pip install jupyter notebook -i https://pypi.tuna.tsinghua.edu.cn/simple 2、测试安装成功 安装完后输入 jupyter notebook 出现一个jupyter网址&#xff0c;即证明安装成功 3、数据迁移 将之前的jupyter notebook产生的文件复制在python所安装的盘目录下。然…

修正的判定条件覆盖例题_语句覆盖、判断覆盖、条件覆盖、条件判定组合覆盖、多条件覆盖、修正条件覆盖...

int function(bool a,bool b,boolc){intx;x0;if(a&&(b||c)){x1;returnx;}}1、语句覆盖(SC)选择足够多的测试数据&#xff0c;使得被测程序中的每条语句至少执行一次。测试用例&#xff1a;aT,bT,cT2、判断覆盖(DC)设计足够的测试用例&#xff0c;使得程序中的每个判定至…

STL2-类模板

1、类模板实现 函数模板在调用时可以自动类型推导 类模板必须显式指定类型 #include<iostream> using namespace std;template<class T> class Person { public:T mId;T mAge; public:Person(T id,T age){this->mAge age;this->mId id;}void Show(){cout…

STL3-MyArray动态数组类模板实现

注意 1、右值的拷贝使用 2、拷贝构造函数的使用 #include<iostream> using namespace std;template<class T> class MyArray{ public:MyArray(int capacity){this->mCapacity capacity;this->mSize 0;//申请内存this->pAddr new T[this->mCapac…

mysql udf提权hex_Mysql_UDF提权

Mysql_UDF提权作者&#xff1a;admin 发布于&#xff1a;2013-5-25 18:55 Saturday分类&#xff1a;MYSQLRoot权限一、上传udf.dll小于mysql5.1版本C:\\WINDOWS\\udf.dll 或C:\\WINDOWS\\system32\\udf.dll等于mysql5.1版本%mysql%\\plugin\\udf.dll 用 selectplugin_dir 查询…

STL4-类型转换

#include<iostream> using namespace std;class Building{}; class Animal{}; class Cat :public Animal {}; //Cat是Animal的子类//static_cast //用于内置的数据类型及具有继承关系的指针或者引用 void test01() {int a 97;//static_cast<要转换的类型>(转换的…

线程池写入mysql_用多线程写入数据库的问题(150分)

把 一少的程序稍加修改就可以多个线程处理一个文件了unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls;typeTForm1 class(TForm)Button1: TButton;procedure FormCreate(Sender: TObject);procedure Bu…

java对象排序_Java™ 教程(对象排序)

对象排序List l可以如下排序。Collections.sort(l);如果List包含String元素&#xff0c;它将按字母顺序排序&#xff0c;如果它由Date元素组成&#xff0c;它将按时间顺序排序&#xff0c;这是怎么发生的&#xff1f;String和Date都实现了Comparable接口&#xff0c;Comparable…

python argparse模块

argparse模块 argparse是python用于解析命令行参数和选项的标准模块&#xff0c;用于代替已经过时的optparse模块 使用步骤 import argparse # 1 导入模块&#xff0c;这个没什么说的 parser argparse.ArgumentParser() # 2 实例化一个对象&#xff0c;默认参数一堆&#…

java解析yml文件_如何基于JAVA读取yml配置文件指定key内容

这篇文章主要介绍了如何基于JAVA读取yml配置文件指定key内容,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下先引入需要的依赖org.yamlsnakeyaml1.23读取YML文件工具类的代码import org.apache.commons.lang3.St…

STL5-异常

异常可以跨函数 异常必须处理 1、 #include<iostream> using namespace std; //c异常机制 跨函数 //c异常必须处理 不能留&#xff0c;否则报错 int divided(int x, int y) {if (y 0)throw y; //抛异常return (x / y); } void test01() {int x 10, y 0;//试着去捕获…

java 并发组件_Java 并发计数组件Striped64详解

作者&#xff1a; 一字马胡转载标志 【2017-11-03】更新日志日期更新内容备注2017-11-03添加转载标志持续更新Java Striped64Striped64是在java8中添加用来支持累加器的并发组件&#xff0c;它可以在并发环境下使用来做某种计数&#xff0c;Striped64的设计思路是在竞争激烈的时…

ubuntu的MySQL远程数据库连接问题查找

1、开放端口3306 2、添加权限 3、服务器本身没有在安全组规则中开放权限 添加安全组规则后重试。

java中集合怎么定义_Java集合系列(一):集合的定义及分类

1. 集合的定义什么是集合呢&#xff1f;定义&#xff1a;集合是一个存放对象的引用的容器。在Java中&#xff0c;集合位于java.util包下。2. 集合和数组的区别(面试常问)提到容器&#xff0c;就会想起数组&#xff0c;那么集合和数组的区别是什么呢&#xff1f;(这里是重点&…

STL6-输入输出流

cout 是 console output 缩写 程序 和键盘 之间有一个输入缓冲区 程序 和 显示器 之间有一个输出缓冲区 #include<iostream> #include<windows.h> #include<iomanip> using namespace std; #if 0 cout << "dd"; //全局流对象&#xff0c;默…

azul java_Java版本更新重大提醒 - Azul

2019年1月对于Java开发人员和用户都特别重要。原因是Oracle JDK 8发布了更新202&#xff0c;这是Oracle的JDK 8的最终公开更新(针对商业用户)。用户现在需要仔细考虑将来如何更新升级Java版本了。对于非商业桌面用户&#xff0c;将继续通过现有Java Update机制提供对Oracle Jav…