• C/C++预定义宏、 #line 、#error、 #pragma和泛型选择


    预定义宏

    C标准规定了一些预定义宏:
    预 定 义 宏

    _ _func_ _是C语言的预定义标识符

    C99 标准提供一个名为_ _func_ _的预定义标识符,它展开为一个代表
    函数名的字符串(该函数包含该标识符)。那么,_ _func_ _必须具有函数
    作用域,而从本质上看宏具有文件作用域。因此,_ _func_ _是C语言的预定
    义标识符,而不是预定义宏。

    下面程序中使用了一些预定义宏和预定义标识符。注意,其中一
    些是C99 新增的,所以不支持C99的编译器可能无法识别它们。如果使用
    GCC,必须设置-std=c99或-std=c11。

    predef.c程序:

    // predef.c -- 预定义宏和预定义标识符
    #include 
    void why_me();
    int main(){
    	printf("The file is %s.\n", __FILE__);
    	printf("The date is %s.\n", __DATE__);
    	printf("The time is %s.\n", __TIME__);
    	printf("The version is %ld.\n", __STDC_VERSION__);
    	printf("This is line %d.\n", __LINE__);
    	printf("This function is %s\n", __func__);
    	why_me();
    	return 0;
    }
    void why_me(){
    	printf("This function is %s\n", __func__);
    	printf("This is line %d.\n", __LINE__);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    下面是该程序的输出:

    The file is predef.c.
    The date is Sep 23 2013.
    The time is 22:01:09.
    The version is 201112.
    This is line 11.
    This function is main
    This function is why_me
    This is line 21.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    #line和#error

    #line指令重置_ _LINE_ __ _FILE_ _宏报告的行号和文件名。可以这
    样使用#line:

    #line 1000 // 把当前行号重置为1000
    #line 10 "cool.c" // 把行号重置为10,把文件名重置为cool.c
    
    • 1
    • 2

    #error 指令让预处理器发出一条错误消息,该消息包含指令中的文本。
    如果可能的话,编译过程应该中断。可以这样使用#error指令:

    #if _ _STDC_VERSION_ _ != 201112L
    #error Not C11
    #endif
    
    • 1
    • 2
    • 3

    编译以上代码生成后,输出如下:

    $ gcc newish.c
    newish.c:14:2: error: #error Not C11
    $ gcc -std=c11 newish.c
    $
    
    • 1
    • 2
    • 3
    • 4

    如果编译器只支持旧标准,则会编译失败,如果支持C11标准,就能成
    功编译。

    #pragma

    在现在的编译器中,可以通过命令行参数或IDE菜单修改编译器的一些
    设置。#pragma把编译器指令放入源代码中。例如,在开发C99时,标准被
    称为C9X,可以使用下面的编译指示(pragma)让编译器支持C9X:

    #pragma c9x on
    
    • 1

    一般而言,编译器都有自己的编译指示集。例如,编译指示可能用于控
    制分配给自动变量的内存量,或者设置错误检查的严格程度,或者启用非标
    准语言特性等。C99 标准提供了 3 个标准编译指示。

    C99还提供_Pragma预处理器运算符,该运算符把字符串转换成普通的
    编译指示。例如:

    _Pragma("nonstandardtreatmenttypeB on")
    
    • 1

    等价于下面的指令:

    #pragma nonstandardtreatmenttypeB on
    
    • 1

    由于该运算符不使用#符号,所以可以把它作为宏展开的一部分:

    #define PRAGMA(X) _Pragma(#X)
    #define LIMRG(X) PRAGMA(STDC CX_LIMITED_RANGE X)
    
    • 1
    • 2

    然后,可以使用类似下面的代码:

    LIMRG ( ON )
    
    • 1

    顺带一提,下面的定义看上去没问题,但实际上无法正常运行:

    #define LIMRG(X) _Pragma(STDC CX_LIMITED_RANGE #X)
    
    • 1

    问题在于这行代码依赖字符串的串联功能,而预处理过程完成之后才会
    串联字符串。

    _Pragma 运算符完成“解字符串”(destringizing)的工作,即把字符串中
    的转义序列转换成它所代表的字符。因此,

    _Pragma("use_bool \"true \"false")
    
    • 1

    变成了:

    #pragma use_bool "true "false
    
    • 1

    泛型选择(C11)

    在程序设计中,泛型编程(generic programming)指那些没有特定类
    型,但是一旦指定一种类型,就可以转换成指定类型的代码。例如,C++在
    模板中可以创建泛型算法,然后编译器根据指定的类型自动使用实例化代
    码。

    C没有这种功能。然而,C11新增了一种表达式,叫作泛型选择表达式
    (generic selection expression),可根据表达式的类型(即表达式的类型是
    int、double 还是其他类型)选择一个值。泛型选择表达式不是预处理器指
    令,但是在一些泛型编程中它常用作#define宏定义的一部分。
    下面是一个泛型选择表达式的示例:

    _Generic(x, int: 0, float: 1, double: 2, default: 3)
    
    • 1

    _Generic是C11的关键字。_Generic后面的圆括号中包含多个用逗号分隔
    的项。第1个项是一个表达式,后面的每个项都由一个类型、一个冒号和一
    个值组成,如float: 1。第1个项的类型匹配哪个标签,整个表达式的值是该
    标签后面的值。例如,假设上面表达式中x是int类型的变量,x的类型匹配
    int:标签,那么整个表达式的值就是0。如果没有与类型匹配的标签,表达式
    的值就是default:标签后面的值。泛型选择语句与 switch 语句类似,只是前
    者用表达式的类型匹配标签,而后者用表达式的值匹配标签。
    下面是一个把泛型选择语句和宏定义组合的例子:

    #define MYTYPE(X) _Generic((X),\
    int: "int",\
    float : "float",\
    double: "double",\
    default: "other"\
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    宏必须定义为一条逻辑行,但是可以用 \ 把一条逻辑行分隔成多条物理
    行。

    在这种情况下,对泛型选择表达式求值得字符串。例如,对
    MYTYPE(5)求值得"int",因为值5的类型与int:标签匹配。

    下面程序演示了这种用法:

    mytype.c程序

    // mytype.c
    #include 
    #define MYTYPE(X) _Generic((X),\
    int: "int",\
    float : "float",\
    double: "double",\
    default: "other"\
    )
    int main(void)
    {
    int d = 5;
    printf("%s\n", MYTYPE(d)); // d 是int类型
    printf("%s\n", MYTYPE(2.0*d)); // 2.0 * d 是double类型
    printf("%s\n", MYTYPE(3L)); // 3L是long类型
    printf("%s\n", MYTYPE(&d)); // &d 的类型是 int *
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    下面是该程序的输出:

    int
    double
    other
    other
    
    • 1
    • 2
    • 3
    • 4

    MYTYPE()最后两个示例所用的类型与标签不匹配,所以打印默认的字
    符串。可以使用更多类型标签来扩展宏的能力,但是该程序主要是为了演示
    _Generic的基本工作原理。

    对一个泛型选择表达式求值时,程序不会先对第一个项求值,它只确定
    类型。只有匹配标签的类型后才会对表达式求值。

    可以像使用独立类型(“泛型”)函数那样使用_Generic 定义宏。

    参考

    《C Primer Plus》

  • 相关阅读:
    Linux CentOS7配置网络参数
    js逆向--mytokencap.com站点code参数的破解
    记一次doc、docx转html的过程
    华为云云耀云服务器L实例评测|Elasticsearch的Docker版本的安装和参数设置 & 端口开放和浏览器访问
    【11】c++11新特性 —>move移动语义(2)
    HTB靶机:RainyDay
    CVPR‘23投稿量再创新高? CCF会议投稿量大比拼, 谁才是卷王?
    linux系统定时任务与延迟任务
    vscode基于cmake安装opencv库
    2024年1月行车记录仪线上行业分析报告:高端化、智能化趋势已现
  • 原文地址:https://blog.csdn.net/Shujie_L/article/details/134447070