• 【BOOST C++】教程3:变量和宏


    目录

    一、提要

    二、将可以变长度的参数传递给宏

    三、多行宏 Multiline macros in C

    四、CRASH() 宏——解释

    五、带开关的宏( OFFSETOF() macro)

    六、带分支预测的宏(Branch prediction macros in GCC)


    一、提要

            只学习一个C语言可能“没用”,但是如果你是搞AI的,或是深度学习、或是自动驾驶、或是机器人,达到一定程度,都能发现C++的不足。因此,凡是搞人工智能的,业余时间磨一磨C++是有益的。

            在C语言中,宏是较难掌握的要点。凡涉及到宏的语句,必须写上好几遍才能正确。本文介绍BOOST C++的宏几个用法,比传统C又有许多特点。

    二、将可以变长度的参数传递给宏

            像函数一样,我们也可以将可变长度参数传递给宏。为此,我们将使用以下预处理器标识符。
            为了支持宏中的可变长度参数,我们必须在宏定义中包含省略号 (...)。还有“__VA_ARGS__”预处理标识符,它负责提供给宏的可变长度参数替换。连接运算符##(又名粘贴运算符)用于连接变量参数。
            让我们用例子来看看。下面的宏采用可变长度参数,如“printf()”函数。此宏用于错误记录。宏打印文件名后跟行号,最后打印信息/错误消息。第一个参数“prio”决定消息的优先级,即是信息消息还是错误,“stream”可以是“标准输出”或“标准错误”。它在 stdout 上显示 INFO 消息,在 stderr 流上显示 ERROR 消息。

    参考代码:

    1. #include
    2. #define INFO     1
    3. #define ERR       2
    4. #define STD_OUT    stdout
    5. #define STD_ERR    stderr
    6. #define LOG_MESSAGE(prio, stream, msg, ...) do {\
    7.      char *str;\
    8.     if (prio == INFO)\
    9.        str = "INFO";\
    10.     else if (prio == ERR)\
    11.        str = "ERR";\
    12.        fprintf(stream, "[%s] : %s : %d : "msg" \n", \
    13.           str, __FILE__, __LINE__, ##__VA_ARGS__);\
    14.                     } while (0)
    15. int main(void)
    16. {
    17.     char *s = "Hello";
    18.         /* display normal message */
    19.     LOG_MESSAGE(ERR, STD_ERR, "Failed to open file");
    20.     /* provide string as argument */
    21.     LOG_MESSAGE(INFO, STD_OUT, "%s Geeks for Geeks", s);
    22.     /* provide integer as arguments */
    23.     LOG_MESSAGE(INFO, STD_OUT, "%d + %d = %d", 10, 20, (10 + 20));
    24.     return 0;
    25. }

            编译并运行上面的程序,它会产生以下结果。

    三、多行宏 Multiline macros in C

            在本文中,我们将讨论如何编写多行宏。我们可以写出类似函数的多行宏,但每条语句都以“\”结尾。让我们用例子来看看。下面是一个简单的宏,它接受用户输入的数字,并打印输入的数字是偶数还是奇数。

    1. #include
    2. #define MACRO(num, str) {\
    3.             printf("%d", num);\
    4.             printf(" is");\
    5.             printf(" %s number", str);\
    6.             printf("\n");\
    7.            }
    8. int main(void)
    9. {
    10.     int num;
    11.     printf("Enter a number: ");
    12.     scanf("%d", &num);
    13.     if (num & 1)
    14.         MACRO(num, "Odd");
    15.     else
    16.         MACRO(num, "Even");
    17.     return 0;
    18. }

    乍一看,代码看起来不错,但是当我们尝试编译这段代码时,它给出了编译错误。

    [narendra@/media/partition/GFG]$ make macro
    cc     macro.c   -o macro
    macro.c: In function ‘main’:
    macro.c:19:2: error: ‘else’ without a previous ‘if’
    make: *** [macro] Error 1
    [narendra@/media/partition/GFG]$ 
    

            让我们看看我们在编写宏时犯了什么错误。我们将宏括在花括号中。根据 C 语言规则,每个 C 语句都应该以分号结尾。这就是为什么我们用分号结束 MACRO。这是一个错误。让我们看看 compile 是如何扩展这个宏的。

    if (num & 1)
    {
        -------------------------
        ---- Macro expansion ----
        -------------------------
    };    /* Semicolon at the end of MACRO, and here is ERROR */
    
    else 
    {
       -------------------------
       ---- Macro expansion ----
       -------------------------
    
    };
    

            我们用分号结束了宏。当编译器扩展宏时,它会在“if”语句之后放置分号。由于“if 和 else 语句”之间的分号,编译器给出了编译错误。如果我们忽略“其他”部分,上述程序将正常工作。

            为了克服这个限制,我们可以将宏包含在“do-while(0)”语句中。我们修改后的宏将如下所示。

    1. #include
    2. #define MACRO(num, str) do {\
    3.             printf("%d", num);\
    4.             printf(" is");\
    5.             printf(" %s number", str);\
    6.             printf("\n");\
    7.            } while(0)
    8.   
    9. int main(void)
    10. {
    11.     int num;
    12.     printf("Enter a number: ");
    13.     scanf("%d", &num);
    14.     if (num & 1)
    15.         MACRO(num, "Odd");
    16.     else
    17.         MACRO(num, "Even");
    18.     return 0;
    19. }

    编译并运行上面的代码,现在这段代码可以正常工作了。

    [narendra@/media/partition/GFG]$ make macro
    cc     macro.c   -o macro
    [narendra@/media/partition/GFG]$ ./macro 
    Enter a number: 9
    9 is Odd number
    [narendra@/media/partition/GFG]$ ./macro 
    Enter a number: 10
    10 is Even number
    [narendra@/media/partition/GFG]$ 
    

            我们在“do – while(0)”循环中包含了宏,并且在while结束时,我们将条件设置为“while(0)”,这就是为什么这个循环只会执行一次。

            类似地,我们可以将多行宏括在括号中,而不是“do – while(0)”循环。通过使用这个技巧,我们可以达到同样的效果。让我们看看例子。

    1. #include
    2.   
    3. #define MACRO(num, str) ({\
    4.             printf("%d", num);\
    5.             printf(" is");\
    6.             printf(" %s number", str);\
    7.             printf("\n");\
    8.            })
    9.   
    10. int main(void)
    11. {
    12.     int num;
    13.    printf("Enter a number: ");
    14.     scanf("%d", &num);
    15.     if (num & 1)
    16.         MACRO(num, "Odd");
    17.     else
    18.         MACRO(num, "Even");
    19.     return 0;
    20. }
    [narendra@/media/partition/GFG]$ make macro
    cc     macro.c   -o macro
    [narendra@/media/partition/GFG]$ ./macro 
    Enter a number: 10
    10 is Even number
    [narendra@/media/partition/GFG]$ ./macro 
    Enter a number: 15
    15 is Odd number
    [narendra@/media/partition/GFG]$ 
    

            本文由 Narendra Kangralkar 编译。如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。

    四、CRASH() 宏——解释

    下面给出了来自开源项目的一小段代码

    1. #ifndef __cplusplus
    2. typedef enum BoolenTag
    3. {
    4.    false,
    5.    true
    6. } bool;
    7.   
    8. #endif
    9. #define CRASH() do { \
    10.       ((void(*)())0)(); \
    11.    } while(false)
    12. int main()
    13. {
    14.    CRASH();
    15.    return 0;
    16. }

            你能解释一下上面的代码吗?很简单,下面给出一步一步的方法,语句 while(false) 仅用于测试目的。考虑以下操作,

    ((void(*)())0)();

    可以如下实现,

    0;                      /* literal zero */
    (0); ( ()0 );                /* 0 being casted to some type */
    ( (*) 0 );              /* 0 casted some pointer type */
    ( (*)() 0 );            /* 0 casted as pointer to some function */
    ( void (*)(void) 0 );   /* Interpret 0 as address of function 
     taking nothing and returning nothing */
    ( void (*)(void) 0 )(); /* Invoke the function */

            因此,给定的代码正在调用其代码存储在位置零的函数,换句话说,尝试执行存储在位置零的指令。在具有内存保护 (MMU) 的系统上,操作系统将抛出异常(分段错误),而在没有这种保护的系统(小型嵌入式系统)上,它将执行并且错误将进一步传播。

    五、带开关的宏( OFFSETOF() macro)

            我们知道结构中的元素将按其声明的顺序存储。如何提取结构中元素的位移?我们可以使用 offsetof 宏。通常我们将结构和联合类型(或具有普通构造函数的类)称为普通旧数据 (POD) 类型,它们将用于聚合其他数据类型。以下非标准宏可用于从结构变量的基地址获取元素的位移(以字节为单位)。

    #define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))

            零被转换为结构类型并访问所需元素的地址,该地址被转换为 size_t。根据标准,size_t 是 unsigned int 类型。整体表达式会产生在结构中放置 ELEMENT 之后的字节数。例如,以下代码返回 16 个字节(在 32 位机器上考虑填充)作为结构 Pod 中字符变量 c 的位移。

    • C++
    • C

            在上面的代码中,以下表达式将返回结构 PodType 中元素 c 的位移。

    OFFSETOF(PodType, c);

    在预处理阶段之后,上述宏扩展为

    • c

    ((size_t)&(((PodType *)0)->c))

            由于我们将 0 视为结构变量的地址,因此 c 将放置在其基地址的 16 个字节之后,即 0x00 + 0x10。对结构元素(在本例中为 c)应用 & 会返回元素的地址,即 0x10。将地址转换为 unsigned int (size_t) 会导致元素放置在结构中的字节数。注意:我们可能认为地址运算符 & 是多余的。如果宏中没有地址运算符,代码会取消引用位于 NULL 地址的结构元素。它会在运行时导致访问冲突异常(分段错误)。请注意,根据编译器行为,还有其他方法可以实现 offsetof 宏。最终目标是提取元素的位移。我们将在另一篇文章中看到在喜欢的列表中使用 offsetof 宏来连接相似对象(例如线程池)的实际用法。文基编译的文章。如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。参考:

    1. Linux Kernel code. 2. offsetof Macro | Microsoft Docs 3. GNU C/C++ Compiler Documentation

    六、带分支预测的宏(Branch prediction macros in GCC)

            Linux 内核中最常用的优化技术之一是“__builtin_expect”。在使用条件代码(if-else 语句)时,我们通常知道哪个分支是真的,哪个不是。如果编译器提前知道这些信息,它可以生成最优化的代码。 让我们看看 linux 内核代码中“likely()”和“unlikely()”宏的宏定义“LXR linux/include/linux/compiler.h” [line no 146 and 147].

    #define likely(x)      __builtin_expect(!!(x), 1)

    #define unlikely(x)    __builtin_expect(!!(x), 0)

    在以下示例中,我们将分支标记为可能为真:

    const char *home_dir ;

      

    home_dir = getenv("HOME");

    if (likely(home_dir)) 

        printf("home directory: %s\n", home_dir);

    else

        perror("getenv");

            对于上面的例子,我们将“if”条件标记为“likely()”为真,因此编译器将在分支之后立即放置真代码,而在分支指令中放置假代码。这样编译器就可以实现优化。但不要盲目使用“likely()”和“unlikely()”宏。如果预测正确,则说明跳转指令的周期为零,但如果预测错误,则需要几个周期,因为处理器需要刷新它的管道,这比没有预测更糟糕。

            与其他 CPU 操作相比,访问内存是最慢的 CPU 操作。为了避免这种限制,CPU 使用“CPU 缓存”,例如 L1-cache、L2-cache 等。缓存背后的想法是,将部分内存复制到 CPU 本身。我们可以比任何其他内存更快地访问缓存内存。但问题是,“缓存内存”的大小有限,我们无法将整个内存复制到缓存中。因此,CPU 必须猜测在不久的将来将使用哪个内存并将该内存加载到 CPU 缓存中,上面的宏提示将内存加载到 CPU 缓存中。

            本文由 Narendra Kangralkar 编译。如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。

  • 相关阅读:
    【腾讯云原生降本增效大讲堂】作业帮云原生降本增效实践之路
    阿里云ECS香港服务器性能强大_安全可靠香港免备案服务器
    杭州动环监控系统供应商,动环监控设备
    ubuntu 下安装sqlite3
    LeetCode Cookbook 树(2)
    【性能测试】使用JMeter对code论坛进行压力测试
    注意力机制 - 注意力提示
    vs2022 快捷键设置
    neo4j创建新数据库
    2023年MathorCup数学建模D题航空安全风险分析和飞行技术评估问题解题全过程文档加程序
  • 原文地址:https://blog.csdn.net/gongdiwudu/article/details/126830928