• 预处理


    由我们写好的源代码到CPU可以认识并执行的二进制中间经过很多步骤。

    1.从源代码到可执行程序的过程
    (1)源代码.c文件先经过预处理器,生成一个中间文件.i文件
    (2).i文件经过编译生成汇编.s文件
    (3).s的汇编文件经过汇编器生成.o的目标文件
    (4).o的目标文件经过链接器生成.elf可执行程序

    每一步都有其相应的工具,预处理有预处理器,编译有编译器,链接有链接器,这些工具合在一起叫做编译工具链。在windos上的编译器,这些都被所谓的编译器屏蔽了。只留出一个编译的功能,其实叫编译器是不准确的。在Linux中,gcc就是一个编译工具链。gcc包含预处理器,编译器,汇编器,链接器等。

    2.gcc中只预处理不编译的方法

    gcc xx.c -o 由源码到可执行程序
    gcc xx.c -c -o 只编译不连接,生产.o的目标文件
    gcc -E xx.c -o xx.i 只预处理产生的中间文件

    看只预处理的中间文件有利于我们调试和研究其预处理过程,还记得我们之前说过的#define和typedef的细节差异吗?现在我们就来分析一下,并且看看预处理是个什么过程。

    #include<stdio.h>
    
    #define pchar char*
    void main(void)
    {
        pchar p1,p2;
    }
    
    //进行预处理操作,gcc -E xx.c -o xx.i 产生中间文件
    
    #include<stdio.h>
    
    
    void main(void)
    {
        char* p1,p2;
    }

    分析:
    (1)经过预处理后,原来的#define 代码不见了,直接在其所在位置进行替换
    (2)#define的替换是简单的替换,就是把所有的pchar 用char*替换
    (3)我们看p1,p2的类型。p1shi char*类型的,而p2只是char类型的

    再来看typedef的使用

    #include<stdio.h>
    
    typedef char* pchar
    void main(void)
    {
        pchar p1,p2;
    }
    
    //预处理后
    
    typedef char * pchar;
    
    void main(void )
    {
    
     pchar p1,p2;
    
    }

    我们发现没有变化。也就是说typedef重定义的类型在预处理阶段是不产生变化的,而是在编译阶段发挥作用的。在编译阶段就把pchar当作普通的char*使用。

    3.头文件在预处理阶段发生什么变化?
    (1)有两种包含方式 第一种:#include<> 第二种:#include””
    第一种是用来包含系统提供的头文件的
    第二种是用来包含程序员自己写的头文件的

    (2)两种包含的区别
    第一种:#include< > 的方式,C语言编译器只会到系统指定的目录(编译器配置的目录)去寻找这个头文件,如果找不到就出错。但是编译器允许使用 - i命令添加更多的目录到该系统目录。(不会到当前目录去找)

    第二种方式:#include”“的方式,编译器先当前目录下去寻找,如果没有找到则到系统指定的目录去找。这种方式也可以包含系统文件。

    总结:(1)虽然可以使用“”来包含系统头文件,但是一般来说,遵从一定的规则。系统文件用<>,自己写的用“”,如果自己写的一大群头文件,也使用<>,把该目录添加到编译器指定的目录去即可。
    (2)(头文件包含的实质)在1.c中包含1.h,就是把1.h中的代码原封不动的写到1.c中#include<>语句的原地方去。

    —-1.c文件—-

    # nclude"1.h"
    main()
    {
        c = a + b;
        printf("c = %d
    ",c);
    }
    

     ----1.h文件----

    int a = 1;
    int b = 2;
    int c = 0;  
    
    预处理的命令:gcc -E 1.c -o 1.i

    结果

    # 1 "1.c"
    # 1 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 1 "<command-line>" 2
    # 1 "1.c"
    # 1 "1.h" 1
    int a = 1;
    int b = 2;
    int c = 0;
    # 2 "baohan.c" 2
    main()
    {
     c = a + b;
     printf("c = %d
    ",c);
    }

    分析:
    (1)前面多了很多我们不认识的东西,不用管它,那是给编译器看的。
    (2)在1.c 的#include”1.h”这句代码处,把1.h中的代码全部拷贝到了这里。
    (3)需要注意的是,我们写的代码进过预处理后产生了一个.i文件,所有的改动都是在.i文件中。原来的.c文件补货被改动的。

    4.注释在预处理阶段的变化

    两种条件编译#iddef 和#if( )

    #include<stdio.h>
    //声明函数
    void test(void);
    void main()
    {
        //定义变量
        int a = 1;
        int b = 2;
    }

    经过预处理后

    #include<stdio.h>
    
    void test(void);
    void main()
    {
    
        int a = 1;
        int b = 2;
    }

    我们发现所有的注释都被删除了。

    总结:
    (1)注释是给人看的,编译器不用看。
    (2)在预处理阶段,预处理器会全部删掉注释。也就是说在编译的时候,编译器根本不知道什么是注释。
    (3)预处理器改的是预处理后的一个中间文件.i,而不是源代码.c

    5.条件编译在预处理阶段发生了什么?

    #include<stdio.h>
    
    #define test 3 //宏定义 test 为3 
    
    void main(void)
    {
    
            #ifdef test 3   //判断 test是不是被宏定义为3,是
            printf(" test 3  !
    ");
    
            #else           //不是
    
            printf("else !
    ");
    
            #endif
    
    }
    

     预处理后

    预处理后被包含进来的stdio.h没有写出来,太长了。

    void main(void)
    {
    
    
     printf(" test 3  !
    ");
    
    }

    分析:在预处理阶段,条件编译,会判断哪个条件成立,把条件成立的那个语句留下,不成的条件的那个被舍去。(在.i文件中,不是.c文件)

    再来看第二种条件编译 #if()

    #include<stdio.h>
    
    #define ok 1
    
    main()
    {
            #if(ok)
    
            printf("ok
    ");
    
            #else
    
            printf("not ok
    ");
    
            #endif
    }
    //预处理后: main() { printf("ok
    "); } 

    总结:
    第一种#ifdef 是判断是否该符号被宏定义,在舍弃条件未成立的那一部分
    第二种#if() 和if()一样。括号中是一个逻辑真假值。

  • 相关阅读:
    展望未来,总结过去10年的程序员生涯,给程序员小弟弟小妹妹们的一些总结性忠告
    马士兵_JAVA自学之路(为那些目标模糊的码农们)
    Java知识点总结
    解决:对COM组件的调用返回了错误HRESULT E_FAIL
    平差方法
    二进制、八进制、十进制、十六进制之间转换
    解决电脑复选框图标不正确方法
    SQL语句中的Create
    字段的值转换为大小写
    SQL NOW() 函数
  • 原文地址:https://www.cnblogs.com/1024E/p/13209618.html
Copyright © 2020-2023  润新知