zoukankan      html  css  js  c++  java
  • C语言编译预处理


    C语言由源代码生成可执行程序的过程如下:

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

    其中编译预处理阶段,读取C源程序,对其中的预处理指令(以#开头的指令)和特殊符号进行处理。或者说是扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。

    预处理过程先于编译器对源代码进行处理,读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行转换。预处理过程还会删除程序中的注释和多余的空白字符。

    一、预处理指令

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

    预处理指令是以#号开头的代码行,#号必须是该行除了任何空白字符外的第一个字符。

    #后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。

    预处理指令主要有以下三种:

    1)包含文件:将源文件中以#include格式包含的文件复制到编译的源文件中,可以是头文件,也可以是其它的程序文件。

    2)宏定义指令:#define指令定义一个宏,#undef指令删除一个宏定义。

    3)条件编译:根据#ifdef和#ifndef后面的条件决定需要编译的代码。

    二、包含文件

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

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

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

    #include <文件名>
    

    #include "文件名"
    

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

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

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

    #include命令的作用是把指定的文件模块内容插入到#include所在的位置,当程序编译链接时,系统会把所有#include指定的文件链接生成可执行代码。

    #include包含文件,可以是“.h”,表示C语言程序的头文件,也可以是“.c”,表示包含普通C语言源程序。

    三、宏定义指令

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

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

    1、无参数的宏

    其定义格式如下:

    #define 宏名  字符串
    

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

    # 表示这是一条预处理命令(凡是以“#”开始的均为预处理命令)。

    define 关键字“define”为宏定义命令。

    宏名 是一个标示符,必须符合C语言标示符的规定,一般以大写字母标识宏名。

    字符串可以是常数,表达式,格式串等。在前面使用的符号常量的定义就是一个无参数宏定义。

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

    #define PI 3.141592
    

    示例(book149.c)

    /*
     * 程序名:book149.c,此程序演示不带参数的宏。
     * 作者:C语言技术网(www.freecplus.net) 日期:20190525
    */
    
    #define PI 3.141592
    
    int main()
    {
      printf("PI is %lf
    ",PI);
    }
    

    执行预编译指令gcc -E -o book149.E book149.c,得到book149.E文件,如下:

    # 1 "book149.c"
    # 1 "<built-in>"
    # 1 "<命令行>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 1 "<命令行>" 2
    # 1 "book149.c"
    
    int main()
    {
      printf("PI is %lf
    ",3.141592);
    }
    

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

    宏定义是宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何语法检查。如有错误,只能在编译已被宏展开后的源程序时发现。

    宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层替换。建议不要这么做,会把程序复杂化。

    习惯上宏名用大写字母表示,以方便与变量区别。但也可以用小写字母。

    2、带参数的宏

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

    带参宏定义的一般形式为:

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

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

    #define MAX(x,y)  ((x)>(y) ? (x) : (y))
    

    以上的宏定义中,如果x的值大于y,得到x,否则得到y。

    示例(book150.c)

    /*
     * 程序名:book150.c,此程序演示带参数的宏
     * 作者:C语言技术网(www.freecplus.net) 日期:20190525
    */
    
    #define MAX(x,y) ((x)>(y)?(x):(y))
    
    int main()
    {
      printf("MAX is %d
    ",MAX(34,59));
    }
    

    执行预编译指令gcc -E -o book150.E book150.c,得到book50.E文件,如下:

    # 1 "book150.c"
    # 1 "<built-in>"
    # 1 "<命令行>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 1 "<命令行>" 2
    # 1 "book150.c"
    
    int main()
    {
      printf("MAX is %d
    ",((34)>(59)?(34):(59)));
    }
    

    带参的宏和带参的函数相似,但其本质是不同的。使用带参宏时,在预处理时将程序源代码替换到相应的位置,编译时得到完整的目标代码,而不进行函数调用,因此程序执行效率要高些。而函数调用只需要编译一次函数,代码量较少,一般情况下,对于简单的功能,可使用宏替换的形式来使用。

    带参数的宏不容易理解,所以,在实际开发中,我不建议使用带参数的宏。

    四、条件编译

    条件编译有多种格式,在这里我只介绍最常用的两种格式#ifdef和#ifndef。

    1、#ifdef

    #ifdef命令的使用格式如下:

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

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

    示例(book153.c)

    /*
     * 程序名:book153.c,此程序用于演示#ifdef条件编译
     * 作者:C语言技术网(www.freecplus.net) 日期:20190525
    */
    #define LINUX
    
    int main()
    {
      #ifdef LINUX
        printf("这是Linux操作系统。
    ");
      #else
        printf("未知的操作系统。
    ");
      #endif
    }
    

    执行预编译指令gcc -E -o book153.E book153.c,得到book153.E文件,如下:

    # 1 "book153.c"
    # 1 "<built-in>"
    # 1 "<命令行>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 1 "<命令行>" 2
    # 1 "book153.c"
    
    int main()
    {
      printf("这是Linux操作系统。
    ");
    }
    

    2、#ifndef

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

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

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

    在实际开发中,程序员用#ifndef来防止头文件被重复包含。

    我们打开/usr/include/stdio.h文件。

    第一条有效行的代码是。

    #ifndef _STDIO_H
    

    接下来是。

    # define _STDIO_H  1
    

    最后一行是。

    #endif
    

    程序员自定义的头文件,我们也会这么写,如:

    /*
     * 程序名:_public.h,公共功能函数声明的头文件
     * 作者:C语言技术网(www.freecplus.net) 日期:20190525
    */
    #ifndef _PUBLIC_H
    #define _PUBLIC_H 1
    
    // 把字符串格式的时间转换为整数的时间,函数的声明如下:
    int strtotime(const char *strtime,time_t *ti);
    
    #endif
    

    如果头文件被包含多次,就表示头文件中的函数被多次声明,全局变量被多次定义,在以前的C语言编译器中,这是不允许的,编译时会报错,但是,现在的部分编译器比较智能,多次定义全局变量或多次声明函数也不会报错。这些新的特征让我这个老家伙很不适应。

    3、#undef

    undef取消已定义的标识符。

    五、课后作业

    1)编写示例程序,把本章节介绍的知识点全部演示一遍,用程序演示可以加深您的理解和映象。

    2)编写代码测试多次声明同名的函数、多次定义同名的函数、多次声明同名的全局变量、多次声明同名的局部变量。

    六、版权声明

    C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。
    来源:C语言技术网(www.freecplus.net)
    作者:码农有道

    如果文章有错别字,或者内容有错误,或其他的建议和意见,请您留言指正,非常感谢!!!

  • 相关阅读:
    JDBC连接效率问题
    如何配置Filter过滤器处理JSP中文乱码(转)
    Servlet生命周期与工作原理(转)
    ANR触发原理
    SystemServer概述
    Zygote总结
    ART、JIT、AOT、Dalvik之间有什么关系?
    谈谈android缓存文件
    Activity启动过程全解析
    tombstone问题分析
  • 原文地址:https://www.cnblogs.com/wucongzhou/p/12498513.html
Copyright © 2011-2022 走看看