精华内容
下载资源
问答
  • C语言编译过程中错误分析,语言最大特点是:功能强、使用方便灵活。C编译的程序对语法检查并不象其它高级语言那么严格,这就给编程
  • C语言编译的全过程

    2014-03-05 13:23:04
    C语言编译的整个过程是非常复杂的,里面涉及到的编译器知识、硬件知识、工具链知识都是非常多的,深入了解整个C语言编译过程对工程师理解应用程序的编写是有很大帮助的,希望大家可以多了解一些,在遇到问题时多思考...
         C语言编译的整个过程是非常复杂的,里面涉及到的编译器知识、硬件知识、工具链知识都是非常多的,深入了解整个C语言编译过程对工程师理解应用程序的编写是有很大帮助的,希望大家可以多了解一些,在遇到问题时多思考、多实践。
        编译的概念:编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。
        编译的完整过程:C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等)
    1. 编译预处理
        读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理,伪指令主要包括以下四个方面:
    (1)宏定义指令,如#define Name TokenString,#undef等。
        对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的 Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。
    (2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。
        这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
    (3) 头文件包含指令,如#include "FileName"或者#include 等。
        在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。
        采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
        包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。
    (4)特殊符号,预编译程序可以识别一些特殊的符号。
        例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
        预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。
     2. 编译、优化阶段
        经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,/等等。
        编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
        优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。
        对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。 
        后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。
        经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。
    3. 汇编过程
        汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:
        代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
        数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。
        UNIX环境下主要有三种类型的目标文件:
    (1)可重定位文件
        其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
    (2)共享的目标文件
        这种文件存放了适合于在两种上下文里链接的代码和数据。
        第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;
        第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。
    (3)可执行文件
        它包含了一个可以被操作系统创建一个进程来执行之的文件。
        汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。
    4. 链接程序
        由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
        例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
        链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。
        根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
    (1)静态链接
        在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
     (2) 动态链接
        在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
        对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。
    展开全文
  • 算符文法:即它任一产生式右部都不含两个相继非终结符文法。如果G是一个不含空字符算法文法,那么只要它任一对终结符都只满足>,=,<关系一种,则称G是一个算符优先文法。  对于一个算符优先文法,...
  • 编译器看到是预处理器修改过代码文本,C语言的编译预处理功能主要包括宏定义、文件包含和条件编译3种。预处理器对宏进行替换,并将所包含头文件整体插入源文件中,为后面要进行的编译做好准备。 2.编译 ...

    程序运行过程


    文件后缀名规则
    C程序:.c
    C++程序:
    非UNIX系统——.cpp;UNIX系统——.cc


    1.预处理

    预处理的命令以“#”开头。

    “#include”和“#define”都属于编译预处理,C语言允许在程序中用预处理指令写一些命令行。
    预处理器在编译器之前根据指令更改程序文本。

    编译器看到的是预处理器修改过的代码文本,C语言的编译预处理功能主要包括宏定义、文件包含和条件编译3种预处理器对宏进行替换,并将所包含的头文件整体插入源文件中,为后面要进行的编译做好准备

    #include <文件名>或者#include “文件名”

    两种形式的区别在于:使用尖括号表示在系统头文件目录中查找(由用户在设置编程环境时设置),而不在源文件目录中查找。使用双引号则表示首先在当前的源文件目录中查找,找不到再到系统头文件目录中查找。


    #include <.h> 与 #include <>的区别:
    C++是在C语言基础上发展的一门编程语言,而C++ 99%的兼容C,也就是说,一般你写的一个C程序,也属于一个C++程序。C++为了兼容C,继承了原来C语言中的库。如<math.h>, 你可以直接在C++程序中使用。但是呢,C++也对原来的C库做了一些自己的规范,也就是说使用了双重标准,你可以使用原来的C库,但C++推荐使用C++规范下的C库。

    1、由于引入了名称空间这个东西。标准库都在std这个名称空间中。也就是说从C中继承的库都放在了std名称空间中。你不能直接使用额,需要加名称空间限定。
    2、 并且规定:标准库的头文件不再使用.h后缀,而采用无后缀。如果某个库是从C中继承过来的,则去掉后缀.h,并在前面加上c 比如<math.h> 改为。如果是C++特有的库,则不添加c。

    如"vector>" “string”
    如果你使用的是老式的,C标准下的库,比如#include<math.h>,则可以直接使用math库中的函数。因为老式的C库不存在名称空间这个技术。
    如果你使用的是C++标准下的C库,比如 #include “cmath”。需要加名称空间限定 std::sqrt() 。当然如果你使用了using namespace std;则可以直接使用sqrt,而无需加 std::限定


    5个预定义宏(可以直接使用)
    在这里插入图片描述


    2.编译

    编译器处理的对象是由单个c文件和其中递归包含的头文件组成的编译单元,一般来说,头文件是不直接参加编译的。

    编译器会将每个编译单元翻译成同名的二进制代码文件,在DOS和Windows环境下,二进制代码文件的后缀名为.obj,在Unix环境下,其后缀名为.o,此时,二进制代码文件是零散的,还不是可执行二进制文件。

    错误检查大多是在编译阶段进行的,编译器主要进行语法分析,词法分析,产生目标代码并进行代码优化等处理。为全局变量和静态变量等分配内存,并检查函数是否已定义,如没有定义,是否有函数声明。函数声明通知编译器:该函数在本文件晚些时候定义,或者是在其他文件中定义。


    3.链接

    链接器将编译得到的零散的二进制代码文件组合成二进制可执行文件,主要完成下述两个工作,一是解析其他文件中函数引用或其他引用,二是解析库函数。

    展开全文
  • C语言程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。 一.预处理的工作方式 1.1.预处理...

    在C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。

    一.预处理的工作方式

    1.1.预处理的功能

    在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。

    所以,完整的步骤是:预编译 -> 编译 -> 链接

    预编译的主要作用如下:

    1. 将源文件中以”include”格式包含的文件复制到编译的源文件中。
    2. 用实际值替换用“#define”定义的字符串。
    3. 根据“#if”后面的条件决定需要编译的代码。

    1.2预处理的工作方式

    预处理的行为是由指令控制的。这些指令是由#字符开头的一些命令。

    #define指令定义了一个宏—用来代表其他东西的一个命令,通常是某一个类型的常量。预处理会通过将宏的名字和它的定义存储在一起来响应#define指令。当这个宏在后面的程序中使用到时,预处理器”扩展”了宏,将宏替换为它所定义的值。例如:下面这行命令:

    #define PI 3.141592654
    

    #include指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分“包含”进来。例如:下面这行命令:

    #include<stdio.h>
    

    指示预处理器打开一个名字为stdio.h的文件,并将它的内容加到当前的程序中。

    预处理器的输入是一个C语言程序,程序可能包含指令。预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器的输出是另外一个程序:原程序的一个编辑后的版本,不再包含指令。预处理器的输出被直接交给编译器,编译器检查程序是否有错误,并经程序翻译为目标代码。

    二.预处理指令

    2.1.预处理指令

    大多数预处理器指令属于下面3种类型:

    1. 宏定义:#define 指令定义一个宏,#undef指令删除一个宏定义。
    2. 文件包含:#include指令导致一个指定文件的内容被包含到程序中。
    3. 条件编译:#if,#ifdef,#ifndef,#elif,#else和#endif指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外。

    剩下的#error,#line和#pragma指令更特殊的指令,较少用到。

    2.2.指令规则

    1. 指令都是以#开始。#符号不需要在一行的行首,只要她之前有空白字符就行。在#后是指令名,接着是指令所需要的其他信息。
    2. 在指令的符号之间可以插入任意数量的空格或横向制表符。 指令总是第一个换行符处结束,除非明确地指明要继续。
    3. 指令可以出现在程序中任何地方。我们通常将#define和#include指令放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。
    4. 注释可以与指令放在同一行。

    三.宏定义命令----#define

    使用#define命令并不是真正的定义符号常量,而是定义一个可以替换的宏。被定义为宏的标示符称为“宏名”。在编译预处理过程时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。

    在C语言中,宏分为有参数和无参数两种。

    3.1.无参数的宏

    其定义格式如下:

    1

    #define 宏名  字符串
    

    在以上宏定义语句中,各部分的含义如下:

    1. #:表示这是一条预处理命令(凡是以“#”开始的均为预处理命令)
    2. define:关键字“define”为宏定义命令。
    3. 宏名:是一个标示符,必须符合C语言标示符的规定,一般以大写字母标示宏名。
    4. 字符串:可以是常数,表达式,格式串等。在前面使用的符号常量的定义就是一个无参数宏定义。

    **Notice:**预处理命令语句后面一般不会添加分号,如果在#define最后有分号,在宏替换时分号也将替换到源代码中去。在宏名和字符串之间可以有任意个空格。

    #define PI 3.14
    

    在使用宏定义时,还需要注意以下几点:

    1. 宏定义是宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。
    2. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。
    3. 宏名在源程序中若用引号括起来,则预处理程序不对其作宏替换。
    4. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层替换。
    5. 习惯上宏名可用大写字母表示,以方便与变量区别。但也允许用小写字母。

    3.2带参数的宏

    #define命令定义宏时,还可以为宏设置参数。与函数中的参数类似,在宏定于中的参数为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,还要用实参去代换形参。
    带参宏定义的一般形式为:

    #define 宏名(形参表) 字符串  
    

    在定义带参数的宏时,宏名和形参表之间不能有空格出现,否则,就将宏定义成为无参数形式,而导致程序出错。

    #define ABS(x)  (x)<0?-(x):(x)
    

    以上的宏定义中,如果x的值小于0,则使用一元运算符(-)对其取负,得到正数。
      带参的宏和带参的函数相似,但其本质是不同的。使用带参宏时,在预处理时将程序源代码替换到相应的位置,编译时得到完整的目标代码,而不进行函数调用,因此程序执行效率要高些。而函数调用只需要编译一次函数,代码量较少,一般情况下,对于简单的功能,可使用宏替换的形式来使用。

    3.3.预处理操作符#和##

    3.3.1.操作符#

    在使用#define定义宏时,可使用操作符#在字符串中输出实参。Eg:

    #define AREA(x,y) printf(“长为“#x”,宽为“#y”的长方形的面积:%d\n”,(x)*(y));
    

    3.3.2.操作符##

    与操作符#类似,操作符##也可用在带参宏中替换部分内容。该操作符将宏中的两个部分连接成一个内容。例如,定义如下宏:

    #define VAR(n)   v##n 
    

    当使用一下方式引用宏:

    VAR(1) 
    预处理时,将得到以下形式:

     v1
    

    如果使用以下宏定义:

    #define FUNC(n)  oper##n  
    

    当实参为1时,预处理后得到一下形式:

    oper1
    

    四.文件包含------include

    当一个C语言程序由多个文件模块组成时,主模块中一般包含main函数和一些当前程序专用的函数。程序从main函数开始执行,在执行过程中,可调用当前文件中的函数,也可调用其他文件模块中的函数。

    如果在模块中要调用其他文件模块中的函数,首先必须在主模块中声明该函数原型。一般都是采用文件包含的方法,包含其他文件模块的头文件。

    文件包含中指定的文件名即可以用引号括起来,也可以用尖括号括起来,格式如下:

    #include< 文件名>
    或
    #include“文件名”
    

    如果使用尖括号<>括起文件名,则编译程序将到C语言开发环境中设置好的 include文件中去找指定的文件。

    因为C语言的标准头文件都存放在include文件夹中,所以一般对标准头文件采用尖括号;对编程自己编写的文件,则使用双引号。

    如果自己编写的文件不是存放在当前工作文件夹,可以在#include命令后面加在路径。

    #include命令的作用是把指定的文件模块内容插入到#include所在的位置,当程序编译链接时,系统会把所有#include指定的文件链接生成可执行代码。文件包含必须以#开头,表示这是编译预处理命令,行尾不能用分号结束。

    #include所包含的文件,其扩展名可以是“.c”,表示包含普通C语言源程序。也可以是 “.h”,表示C语言程序的头文件。C语言系统中大量的定义与声明是以头文件形式提供的。 “.h”是接口文件,如果想理解C语言接口的写法,有必要琢磨一下 “.h”。

    通过#define包含进来的文件模块中还可以再包含其他文件,这种用法称为嵌套包含。嵌套的层数与具体C语言系统有关,但是一般可以嵌套8层以上。

    五.条件编译

    预处理器还提供了条件编译功能。在预处理时,按照不同的条件去编译程序的不同部分,从而得到不同的目标代码。

    使用条件编译,可方便地处理程序的调试版本和正式版本,也可使用条件编译使程序的移植更方便。

    5.1使用#if

    与C语言的条件分支语句类似,在预处理时,也可以使用分支,根据不同的情况编译不同的源代码段。

    #if 的使用格式如下:

    #if 常量表达式
       程序段
    #else
      程序段
    #endif
    

    该条件编译命令的执行过程为:若常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。

    举个例子:

    #define DEBUG 1
     
    int main()
     
    {
     
       int i,j;
       char ch[26];
     
       for(i='a';j=0;i<='z';i++,j++)
       {
           ch[j]=i;
     
           #if DEBUG
     
              printf("ch[%d]=%c\n",j,ch[j]);
     
           #endif
     
       }
     
       for(j=0;j<26;j++)
       {
           printf("%c",ch[j]);
       }
       return 0;
    }
    

    #if预编译命令还可使用多分支语句格式,具体格式如下:

    #if 常量表达式 1
     
        程序段 1
     
    #elif 常量表达式 2
     
        程序段 2
     
    … …
     
    #elif 常量表达式 n
     
        程序段 n
     
    #else
     
        程序段 m
     
    #endif
    

    关键字#elif与多分支if语句中的else if类似。
    举个例子

    #define os win
     
    #if os=win
     
        #include"win.h"
     
    #elif os=linux
     
        #include"linux.h"
     
    #elif os=mac
     
        #include"mac.h"
     
    #endif
    

    #if和#elif还可以进行嵌套,C89标准中,嵌套深度可以到达8层,而C99允许嵌套达到63层。在嵌套时,每个#endif,#else或#elif与最近的#if或#elif配对。

    #define MAX 100
    #define OLD -1
     
    int main()
    {
    int i;
     
    #if MAX>50
    { 
     
        #if OLD>3
     
        {
     
            i=1;
     
        {
     
        #elif OLD>0
     
        {
     
            i=2;
     
        }
     
        #else
     
        {
     
            i=3;
     
        }
     
        #endif
     
    }
     
    #else
     
    {
     
        #if OLD>3
     
        {
     
            i=4;
     
        }
     
        #elif OLD>4
     
        {
     
            i=5;
     
        }
     
        #else
     
        {
     
            i=6;
     
        }
     
        #endif
     
    }
     
    #endif
     
    return 0;
     
    }
    

    5.2使用#ifdef和#ifndef

    在上面的#if条件编译命令中,需要判断符号常量定义的具体值。在很多情况下,其实不需要判断符号常量的值,只需要判断是否定义了该符号常量。这时,可不使用#if命令,而使用另外一个预编译命令———#ifdef.

    #ifdef命令的使用格式如下:
    #ifdef 标识符
    程序段 1
     
    #else
     
        程序段 2
     
    #endif
    

    其意义是,如果#ifdef后面的标识符已被定义过,则对“程序段1”进行编译;如果没有定义标识符,则编译“程序段2”。一般不使用#else及后面的“程序2”。

    而#ifndef的意义与#ifdef相反,其格式如下:

    #ifndef 标识符
        程序段 1
     
    #else
     
        程序段 2
     
    #endif
    

    其意义是:如果未定义标识符,则编译“程序段1”;否则编译“程序段2”。

    5.3使用#defined和#undef

    与#ifdef类似的,可以在#if命令中使用define来判断是否已定义指定的标识符。例如:

    #if defined 标识符
    程序段 1 
     
    #endif
    

    与下面的标示方式意义相同。

    #ifdef 标识符
        程序段 1
     
    #endif
    

    也可使用逻辑运算符,对defined取反。例如:

    #if ! define 标识符
        程序段 1
     
    #endif
     
    

    与下面的标示方式意义相同。

    #ifndef 标识符
        程序段 1
     
    #endif
     
    

    在#ifdef和#ifndef命令后面的标识符是使用#define进行定义的。在程序中,还可以使用#undef取消对标识符的定义,其形式为:

    #undef 标识符

    举个例子:

    #define MAX 100
     
    ……
     
    #undef MAX
    

    在以上代码中,首先使用#define定义标识符MAX,经过一段程序代码后,又可以使用#undef取消已定义的标识符。使用#undef命令后,再使用#ifdef max,将不会编译后的源代码,因为此时标识符MAX已经被取消定义了。

    六.其他预处理命令

    6.1.预定义的宏名

    ANSI C标准预定义了五个宏名,每个宏名的前后均有两个下画线,避免与程序员定义相同的宏名(一般都不会定义前后有两个下划线的宏)。这5个宏名如下:

    __DATE__:当前源程序的创建日期。
    __FILE__:当前源程序的文件名称(包括盘符和路径)。
    __LINE__:当前被编译代码的行号。
    __STDC__:返回编译器是否位标准C,若其值为1表示符合标准C,否则不是标准C.
    __TIME__:当前源程序的创建时间。  
    

    举个例子:

    #include<stdio.h>
    int main()
    {
       int j;
     
       printf("日期:%s\n",__DATE__);
    
       printf("时间:%s\n",__TIME__};
     
       printf("文件名:%s\n",__FILE__);
     
       printf("这是第%d行代码\n",__LINE__);
     
       printf("本编译器%s标准C\n",(__STD__)?"符合":"不符合");
       return 0;
     
    }
    

    6.2.重置行号和文件名命令------------#line

    使用__LINE__预定义宏名赈灾编译的程序行号。使用#line命令可改变预定义宏__LINE__与__FILE__的内容,该命令的基本形如下:
    #line number[“filename”]

    其中的数字为一个正整数,可选的文件名为有效文件标识符。行号为源代码中当前行号,文件名为源文件的名字。命令为#line主要用于调试以及其他特殊应用。
    举个例子:

    1:#include<stdio.h>
    2:#include<stdlib.h>
     
    4:#line 1000
      
    6:int main()
    7:{
    8:    printf("当前行号:%d\n",__LINE__);
    9:    return 0;
    10:} 
    

    在以上程序中,在第4行中使用#line定义的行号为从1000开始(不包括#line这行)。所以第5行的编号将为1000,第6行为1001,第7行为1002,第8行为1003.

    6.3.修改编译器设置命令 ------------#pragma

    #pragma命令的作用是设定编译器的状态,或者指示编译器完全一些特定的动作。#pragma命令对每个编译器给出了一个方法,在保持与C语言完全兼容的情况下,给出主机或者操作系统专有的特征。其格式一般为:

    #pragma Para
    

    其中,Para为参数,可使用的参数很多,下面列出常用的参数:

    Message参数,该参数能够在编译信息输出窗口中输出对应的信息,这对于源代码信息的控制是非常重要的,其使用方法是:

    #pragma message(消息文本)
    

    当编译器遇到这条指令时,就在编译输出窗口中将消息文本显示出来。

    另外一个使用比较多得pragma参数是code_seg.格式如:

    #pragma code_seg([“section_name”[,section_class]])
    

    它能够设置程序中函数代码存放的代码段,在开发驱动程序的时候就会使用到它。

    参数once,可保证头文件被编译一次,其格式为:

    #pragma once
    

    只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。

    6.4.产生错误信息命令 ------------#error

    #error命令强制编译器停止编译,并输出一个错误信息,主要用于程序调试。其使用如下:

    #error 信息错误  
    

    注意,错误信息不用双括号括起来。当遇到#error命令时,错误信息将显示出来。

    例如,以下编译预处理器命令判断预定义宏__STDC__,如果其值不为1,则显示一个错误信息,提示程序员该编译器不支持ANSI C标准。

    #if __STDC__!=1
     
       #error NOT ANSI C
    #endif
    

    七.内联函数

    在使用#define定义带参数宏时,在调用函数时,一般需要增加系统的开销,如参数传递,跳转控制,返回结果等额外操作需要系统内存和执行时间。而使用带参数宏时,通过宏替换可再编译前将函数代码展开导源代码中,使编译后的目标文件含有多段重复的代码。这样做,会增加程序的代码量,都可以减少执行时间。

    在C99标准钟,还提供另外一种解决方法:使用内联函数。
      在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替代。显然,这种做法不会产生转去转回得问题。都是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标代码量,进而增加空间的开销,而在时间开销上不像函数调用时那么大,可见它是以增加目标代码为代码来换取时间的节省。
      定义内联函数的方法很简单,只要在定义函数头的前面加上关键字inline即可。内联函数的定义与一般函数一样。例如,定于一个两个整数相加的函数:

    #include<stdio.h>
    #include<stdlib.h>
    inline int add(int x,int y);
    inline int add(int x,int y)
    {
       return x+y;
    }
    int main()
    {
       int i,j,k;
     
       printf("请输入两个整数的值:\n");
     
       scanf("%d %d",&i,&j);
     
       k=add(i,j);
     
       printf("k=%d\n",k);
     
       return 0;
     
    }
    

    在程序中,调用函数add时,该函数在编译时会将以上代码复制过来,而不是像一般函数那样是运行时被调用。

    内联函数具有一般函数的特性,它与一般函数所不同之处在于函数调用的处理。一般函数进行调用时,要讲程序执行权转导被调函数中,然后再返回到调用到它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应该注意如下几点:

    1. 在内联函数内部允许用循环语句和开关语句。但是,程序在经过编译之后,这个函数是不会作为内联函数进行调用的。
    2. 内联函数的定义必须出现在内联函数第一次被调用之前。

    其实,在程序中声明一个函数为内联时,编译以后这个函数不一定是内联的,
    即程序只是建议编译器使用内联函数,但是编译器会根据函数情况决定是否使用内联,所以如果编写的内联函数中出现循环或者开关语句,程序也不会提示出错,但那个函数已经不是内联函数了。

    一般都是将一个小型函数作为内联函数。
    转载:http://haore147.cnblogs.com/

    展开全文
  • C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式目标文件过程。链接是把目标文件、操作...
  • C语言编译执行过程

    千次阅读 2019-09-27 10:14:57
    C语言编译器的功能就是将源文件,经过编译、链接之后可以形成可执行文件 那么具体的步骤是什么呢? C源程序头文件-->预编译处理(cpp)-->编译程序本身-->优化程序-->汇编程序-->链接程序-...

    一、C语言的编译执行过程

    我们在C语言编辑的文件是以.c为文件拓展名的,称为源文件;C语言编译器的功能就是将源文件,经过编译、链接之后可以形成可执行文件

    那么具体的步骤是什么呢?

    C源程序头文件-->预编译处理(cpp)-->编译程序本身-->优化程序-->汇编程序-->链接程序-->可执行文件

    二、编译执行过程详解

    第一步:在编辑器中编辑源文件

    第二步:编译预处理;

    预处理的作用就是读取源文件中的字符流,对伪指令和特殊符号进行处理

    预编译程序的功能是将源文件中的特殊内容进行替换,但是不会改变源文件的含义,预编译程序的输出文件将作为下一步操作的原始文件

    伪指令(以#开头的指令)和特殊符号包括以下四种类型:

     1.宏定义指令:#define、#undef......#define Name *** 的功能是将源文件中的Name全部替换为***,而#undef的功能是取消某个宏的定义,使其不再生效

     2.条件定义指令:#ifdef、#ifndef、#endif等,这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译工具对哪些代码进行处理,可以通过此方法过滤掉无用代码

     3.头文件包含指令:#include< >、#include" ",使用头文件的目的是使得某些定义可以被多个源程序引用,预编译程序将头文件中的定义加入到其输出文件中,以便编译程序能够对其进行处理;

     两者的区别为#include< >引用的是系统提供的头文件,而#include" "引用的为用户自定义头文件,其文件存放位置必须和源文件在同一路径

     4.特殊符号:预编译程序可以识别一些特殊符号,例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称

     

    此外,在预处理阶段,编译器会删除注释内容,"//"和"/*"的内容,并且会添加代码行号和文件标识,便于在后续过程中输出报错信息时定位到错误位置

    第三步:编译

    经过预编译程序的处理,其输出文件将只包含变量。如数字、字符串、变量的定义,其工作就是将预编译处理程序进行语法检查和语句检查,确认语句符合规范之后,将其翻译为中间代码或者汇编代码

    第四步:优化

    优化是编译程序中的重要部分,它涉及到的问题不仅包括编译技术,还与机器的硬件环境有很大的关系

    优化过程分为两种:一是对中间代码的优化,另一种是针对目标代码进行的生成进行的优化

    第一种优化的主要方式是删除公共表达式以及循环优化,此方式不依赖计算机硬件平台,主要包括代码外提、强度削弱、变换循环控制条件、已知量的合并、复写传递以及无用赋值的删除

    第二种优化则依赖硬件环境,最主要的问题是充分利用硬件的寄存器来保存有关变量的值,以减少内存访问次数,此外,根据机器硬件的指令特点将代码量减少以及增加代码执行效率也是很重要的问题

    经过优化的代码必须经过汇编程序的汇编转化为机器指令才能够正常执行

    第五步:汇编

    汇编过程的主要功能是将汇编代码转化为机器指令,目标文件存放的就是和源文件相对应的机器指令

    目标文件通常由段组成:数据段和代码段

    代码段中包含的主要是程序的指令,一般文件权限是可读可执行但不可写

    数据段中包含的主要是各种全局变量或静态变量的数据,一般文件权限为可读可写可执行

     

    汇编程序生成的是可重定位文件,其中包含了其它目标文件链接来创建一个可执行文件或共享的目标文件所需的代码和数据

    第六步:链接

     由汇编程序生成的目标文件并不能被直接执行,它可能还存在着许多的问题

     例如:某个源文件中的函数引用了另一个源文件中的内容,或者在程序中调用了函数库中的函数,解决这些问题必须进行链接这一过程

     链接过程的主要功能是将有关目标文件相互连接,也就是将在某文件中对其它文件的引用与另一个文件中此引用的定义链接,使得这些目标文件能够被操作系统装入执行的整体

     根据开发人员同库函数的链接方式的不同, 链接方式可分为:静态链接和动态链接

    • 静态链接:在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行文件中,这样该程序在执行时,相关代码将被装载到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中每个文件含有库中的一个或一组相关函数的代码
    • 动态链接:在这种链接方式下,函数的代码被放到动态链接库或共享对象的某个目标文件中。链接程序所做的就是在最终的可执行程序中记录下共享对象的名字以及相关登记信息。在可执行文件运行时,动态链接库中的所有内容都被映射到相应进程的虚地址空间中,可执行程序再根据相关登记信息找到相关执行代码

     

    经过上述过程,C源程序就转化为了可执行程序,默认可执行程序的名字为a.out(Linux环境下)

    三、编译过程中的文件类型

    •        在Windows平台上,C语言源代码文件一般扩展名为.c,目标文件扩展名一般为.obj,生成的可执行文件扩展名一般为.exe
    •   在Linux平台上,C语言源代码文件一般扩展名为.c,预处理操作后的文件名扩展名一般为.i,编译器生成的汇编代码一般扩展名为.s,生成的可执行文件一般扩展为.out,它是有汇编器生成的,所以默认gcc生成的程序名为a.out意思即为Assembler output
    • UNIX环境下主要有三种类型的目标文件:

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

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

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

    参考链接:http://lavasoft.blog.51cto.com/62575/187229

    展开全文
  • C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式目标文件过程。链接是把目标文件、操作...
  • C语言编译链接

    2016-09-02 15:56:00
    编译是指根据用户写程序代码,经过词法和语法分析,将高级语言编写代码转变为功能有效汇编代码。 编译过程如下: 1、 预编译过程 在c语言的编译过程中,主要是对宏定义、条件编译语句、头文件包含...
  • 自2019年8月开源以来,方舟编程体系已经陆续实现了编译器、引擎和调试器开源,编译器重点功能主要集中在Java应用程序静态编译上。在《方舟编程体系》一文中,提到了方舟项目目标是要构建一个基于MapleIR跨语言...
  • C语言编译和链接

    2020-07-23 22:29:53
    编译是指根据用户写程序代码,经过词法和语法分析,将高级语言编写代码转变为功能有效汇编代码。 编译过程如下: 1、 预编译过程 在c语言的编译过程中,主要是对宏定义、条件编译语句、头文件包含...
  • C语言编译、链接

    2014-11-07 01:25:15
    C语言编译、链接 由C语言编写系统项目,往往按照功能将系统各个实现模块切分到不到C源文件中,甚至将相关功能的一组文件收集到同一个文件夹中。要想在C程序编译成最终一个可执行01文件,就要将这些C文件...
  • C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译是把文本形式源代码翻译为机器语言形式目标文件过程。链接是把目标文件、操作系统...
  • 文章目录1 程序的翻译环境和执行环境1.1 翻译环境1.2 运行环境1.3 Visual Studio开发环境 ...  从源程序翻译成可执行程序的过程需要在翻译环境中完成,实际上在ANSI C(标准C)的任何一种程序功能实现
  •  第一种是通过Ubuntu自带的程序安装功能安装Eclipse,应用程序->Ubtuntu软件中心,搜Eclipse安装即可。  第二种方法是用命令:应用程序->附件->终端  然后输入(中间可能需要你输入密码):  
  • 由于PL/0语言功能简单、结构清晰、可读性强,又具备了一般高级语言必须部分,因而PL/0语言的编译程序能充分体现一个高级语言编译程序实现基本技术和步骤,是一个非常合适的编译程序教学模型。
  • C语言编译过程详解

    2010-09-24 20:58:00
    C语言的编译链接过程是要把我们编写一个C程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式目标文件过程。链接是把目标文件、操作系统...
  • 关于C语言动态库与静态库作用这里就不在详细赘述了,重点说一下如何编译动态库与静态库及使用,一些大型的程序一般拥有着良好架构,各个功能模块之间能够相互协调工作,达到预期效果,但有的程序并不是所有...
  • 以下程序中函数cutpaste功能是将line指向数组中从下标为source开始连续len个字符作为一个子串复制到一个临时数组中,并将这些字符从line指向数组中删除;再将临时数组中子串追加到line指向数组中字符串尾部...
  • C语言编译全过程剖析

    2012-01-11 12:46:40
    其中有一些说法不敢认同,但是总体是在... 编译概念:编译程序读取源程序(字符流),对之进行词法和语法分析,将高级语言指令转换为功能等效汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执
  • C语言的编译过程

    千次阅读 2015-07-16 10:37:38
    编译的功能是将人们能看懂的高级语言,转换成计算机能看懂的二进制语言,可以分为下面的六个阶段: 词法分析阶段:根据语言的词法规则来进行分析,词法的规则可用正规文法或正规式来表示是指有限自动机能识别正规...
  • LCC-Win32 是运行于Windows9x/Me/NT/2000下免费32位C语言编译系统。它原始发布文件只有 3M,但却拥有一个功能强大集成开发环境(IDE)、速度极快编译器与连接器、方便顺手调试器、资源编辑器和版本控制...
  • VC++6.0中如何编译运行C语言程序 VC++6.0是Microsoft公司推出的一个基于Windows系统平台可视化的集成开发环境它的 源程序按C++语言的要求编写并加入了微软提供的功能强大的 MFC(Microsoft Foundation Class)类库具有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,211
精华内容 884
关键字:

c语言编译程序的功能是

c语言 订阅