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

发布于:2022-12-14 ⋅ 阅读:(314) ⋅ 点赞:(0)

目录

一、提要

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

三、多行宏 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 消息。

参考代码:

#include <stdio.h>

#define INFO     1
#define ERR       2
#define STD_OUT    stdout
#define STD_ERR    stderr


#define LOG_MESSAGE(prio, stream, msg, ...) do {\
     char *str;\
    if (prio == INFO)\
       str = "INFO";\
    else if (prio == ERR)\
       str = "ERR";\
       fprintf(stream, "[%s] : %s : %d : "msg" \n", \
          str, __FILE__, __LINE__, ##__VA_ARGS__);\

                    } while (0)

int main(void)
{
    char *s = "Hello";

        /* display normal message */
    LOG_MESSAGE(ERR, STD_ERR, "Failed to open file");

    /* provide string as argument */
    LOG_MESSAGE(INFO, STD_OUT, "%s Geeks for Geeks", s);
    /* provide integer as arguments */
    LOG_MESSAGE(INFO, STD_OUT, "%d + %d = %d", 10, 20, (10 + 20));
    return 0;
}

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

三、多行宏 Multiline macros in C

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

#include <stdio.h>

#define MACRO(num, str) {\
            printf("%d", num);\
            printf(" is");\
            printf(" %s number", str);\
            printf("\n");\
           }

int main(void)
{
    int num;
    printf("Enter a number: ");
    scanf("%d", &num);
    if (num & 1)
        MACRO(num, "Odd");
    else
        MACRO(num, "Even");
    return 0;

}

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

[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)”语句中。我们修改后的宏将如下所示。

#include <stdio.h>

#define MACRO(num, str) do {\
            printf("%d", num);\
            printf(" is");\
            printf(" %s number", str);\
            printf("\n");\
           } while(0)
  
int main(void)
{

    int num;
    printf("Enter a number: ");
    scanf("%d", &num);
    if (num & 1)
        MACRO(num, "Odd");
    else
        MACRO(num, "Even");
    return 0;

}

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

[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)”循环。通过使用这个技巧,我们可以达到同样的效果。让我们看看例子。

#include <stdio.h>

  

#define MACRO(num, str) ({\
            printf("%d", num);\
            printf(" is");\
            printf(" %s number", str);\
            printf("\n");\
           })

  

int main(void)
{
    int num;
   printf("Enter a number: ");
    scanf("%d", &num);

    if (num & 1)
        MACRO(num, "Odd");
    else
        MACRO(num, "Even");
    return 0;

}
[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() 宏——解释

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

#ifndef __cplusplus

typedef enum BoolenTag
{
   false,
   true
} bool;
  
#endif

#define CRASH() do { \
      ((void(*)())0)(); \
   } while(false)

int main()
{
   CRASH();
   return 0;
}

        你能解释一下上面的代码吗?很简单,下面给出一步一步的方法,语句 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 编译。如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。


网站公告

今日签到

点亮在社区的每一天
去签到