zoukankan      html  css  js  c++  java
  • C语言的本质(21)——预处理之三:其它预处理特性及总结

    C标准规定了几个特殊的宏,在不同的地方使用可以自动展开成不同的值,预编译程序对于在源程序中出现的这些串将用合适的值进行替换。这些宏有下面这些:

     __FILE__ 展开为当前源文件的文件名,是一个字符串

    __LINE__ 展开为当前代码行的行号,是一个整数

    __DATE__ 展开为包含当前日期的字符串

    __STDC__ 如果编译器遵循ANSIC标准,它就是个非零值

    __TIME__ 展开为包含当前时间的字符串

    注意:是双下划线,而不是单下划线。


    常用的有__FILE__和__LINE__这两个宏在源代码中不同的位置使用会自动取不同的值,显然不是用#define能定义得出来的,它们是编译器内建的特殊的宏。在打印调试信息时打印这两个宏可以给开发者非常有用的提示。

    #include<stdio.h>
    int main(void)
    {
       printf("HelloWorld!
    ");
       printf("%s
    ",__FILE__);
       printf("%d
    ",__LINE__);
       return 0;
    }

    下面我们自己实现断言assert函数,以理解它的原理。

    /* assert.h standard header */
    #undef assert   /* remove existing definition */
     
    #ifdef NDEBUG
             #defineassert(test) ((void)0)
    #else                  /*NDEBUG not defined */
             void_Assert(char *);
             /*macros */
             #define_STR(x) _VAL(x)
             #define_VAL(x) #x
             #defineassert(test) ((test) ? (void)0 
                       :_Assert(__FILE__ ":" _STR(__LINE__) " " #test))
    #endif

    C标准规定assert应该实现为宏定义而不是一个真正的函数,并且assert(test)这个表达式的值应该是void类型的。首先用#undef assert确保取消前面对assert的定义,然后分两种情况:如果定义了NDEBUG,那么assert(test)直接定义成一个void类型的值,什么也不做;如果没有定义NDEBUG,则要判断测试条件test是否成立,如果条件成立就什么也不做,如果不成立则调用_Assert函数。假设在main.c文件的第33行调用assert(is_sorted()),那么__FILE__是字符串"main.c",__LINE__是整数33,#test是字符串"is_sorted()"。注意_STR(__LINE__)的展开过程:首先展开成_VAL(33),然后进一步展开成字符串"33"。这样,最后_Assert调用的形式是_Assert("main.c"":" "33" " " "is_sorted()"),传给_Assert函数的字符串是"main.c:33is_sorted()"。_Assert函数是我们自己定义的,在另一个源文件中:

    /* xassert.c _Assert function */
    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
     
    void _Assert(char *mesg)
    {                 /*print assertion message and abort */
             fputs(mesg,stderr);
             fputs("-- assertion failed
    ", stderr);
             abort();
    }

    注意,在头文件assert.h中自己定义的内部使用的标识符都以_线开头,例如_STR,_VAL,_Assert,因为我们在模拟C标准库的实现,以_线开头的标识符通常由编译器和C语言库使用,在/usr/include下的头文件中你可以看到大量_线开头的标识符。另外为什么我们不直接在assert的宏定义中调用fputs和abort呢?因为调用这两个函数需要包含stdio.h和stdlib.h,C标准库的头文件应该是相互独立的,一个程序只要包含assert.h就应该能使用assert,而不应该再依赖于别的头文件。_Assert中的fputs向标准错误输出打印错误信息,abort异常终止当前进程。

    现在测试一下我们的assert实现,把assert.h和xassert.c和测试代码main.c放在同一个目录下。

    /* main.c */
    #include "assert.h"
     
    int main(void)
    {
             assert(2>3);
             return0;
    }

    注意#include "assert.h"要用"引号而不要用<>括号,以保证包含的是我们自己写的assert.h而非C标准库的头文件。然后编译运行:

    $ gcc main.c xassert.c
    $ ./a.out
    main.c:6 2>3 -- assertion failed
    Aborted

    #error指令将使编译器显示一条错误信息,然后停止编译。

    #line指令改变_LINE_与_FILE_的内容,它们是在编译程序中预先定义的标识符。

    #line举例:

    #line 100 //初始化行计数器
    #include<stdio.h> //行号100
    int main(void)
    {
        printf("HelloWorld!
    ");
        printf("%d",__LINE__);
        return 0;
    }


    输出104

    #pragma指令没有正式的定义。它预处理指示供编译器实现一些非标准的特性,C标准没有规定#pragma后面应该写什么以及起什么作用,由编译器自己规定。典型的用法是禁止或允许某些烦人的警告信息。有的编译器用#pragma定义一些特殊功能寄存器名,有的编译器用#pragma定位链接地址。如果编译器在代码中碰到不认识的#pragma指示则忽略它,例如gcc的#pragma指示都是#pragma GCC ...这种形式,用别的编译器编译则忽略这些指示。

    预处理指令总结:

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

    预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程序员在程序中用预处理命令来调用这些功能。

    宏定义可以带有参数,宏调用时是以实参代换形参。而不是“值传送”。

    为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。

    文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。

    条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。

    使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。

  • 相关阅读:
    spring中各个模块的作用
    《Spring实战》学习笔记-第四章:面向切面的Spring
    《Spring实战》学习笔记-第四章:面向切面的Spring
    Centos7下永久修改mysql5.6最大连接数
    Prefix-List
    Route-Map
    PBR Lab2
    Lab PBR
    ISIS超载位解决流量黑洞
    ISIS TLV
  • 原文地址:https://www.cnblogs.com/new0801/p/6177073.html
Copyright © 2011-2022 走看看