C语言中宏(macro)的特殊用法

news/2024/11/7 14:42:13/

SimpleScalar的源代码中对macro的运用可以说是炉火纯青,丰富的macro使得代码简介,逻辑清晰,但是也给我这些初学者们阅读代码造成了障碍,下面将几篇找到的相关资料贴出来,希望能对大家有用 @@

C语言中的宏(Macro)是最令人头痛,同时又被某些牛人所青睐的。宏(Macro)这一概念通常出现在编译性语言中,意旨编译期间替代人工做某些简短重复的CODING工作。在编译期间,根据您所使用的编译器不同,根据实际展开能力对宏来逐层展开,最终宏会像我们预先定义的样子来展开代码。

    那么,接下来我就先来介绍一下常用的宏技巧吧(听起来有点老套,不过请耐心看下去,会有收获的)。

    1. 头文件包含守护
        出现在某.h或.hpp .hxx中,
        #ifndef __HTML_PAGE_BUILDER_H__
        #define __HTML_PAGE_BUILDER_H__
                  // 一大堆声明出现在这里
        #endif // #ifndef __HTML_PAGE_BUILDER_H__ (加这样的注释是一种好习惯呕~)

        通常用来使某一头文件在编译期间只被其他源文件只包含一次,从而使整个编译逻辑中所有单元的Dependence清晰明确。当然我所说的Dependence只是我们按宏的表面作用来讲的,具体的Dependence还是要真对不同的编译系统再讨论。

    2. 文件域内/外的常量定义
        为什么说“文件域内/外”呢?因为宏(Macro)根据它定义所在单元(文件)不同,将拥有不同的有效域,或叫不同的外部视野。
        a) 定义在.c/.cpp/.cxx源文件内
            在.c类似的源文件编译单元内,宏只能被该单元可见。也就是说在该单元被第一次预编译时,会先找出单元所属所有的宏,然后在该单元可见,在该单元编译结束时,所有这些宏又全部消失掉。
            从而,我们可以做个实验,以VC的COMPILER为例:
            // my_a.c
            #define __AVAIL_ONLY_IN_MY_A_C__
            // my_b.c
            #if defined(__AVAIL_ONLY_IN_MY_A_C__)
            #error Error will never occurred
            #endif // #if defined(__AVAIL_ONLY_IN_MY_A_C__)

            不管以怎样的编译顺序,在Compile my_b.c时都不会发生错误。

            那这时候您想问,“请问宏的名字能不能在单元内被复用?”,我可以回答说,“能,但有限制”,如下面的用法
           // my_a.c
           #define SOME_FACTOR   (1.0)
               // …
               {
                    // …
                    some_var = calc_with( SOME_FACTOR );
               }
           #undef SOME_FACTOR
           #define SOME_FACTOR   (2.0)
               // …
               {
                    // …
                    some_var = calc_with( SOME_FACTOR );
               }
           #undef SOME_FACTOR

           明白了吧,当您的代码结足够好,逻辑足够清楚时,也可以尝试一下这种用法

           宏定义是没有位置限制的,也可以尝试一下下面的BT用法
           void some_func( void )
           {
               #if defined(SOME_FACTOR)
               #error OH MY GOD, it make the codes so confused!
               #endif // #if defined(SOME_FACTOR)
               #define SOME_FACTOR (1.0)
               // …
               calc_with( SOME_FACTOR );
               // …
                some_var = calc2_with( SOME_FACTOR );
               some_var = calc3_with( SOME_FACTOR );
               // …
               #undef SOME_FACTOR
           }

           这样做的优点就是,在IDE非常弱智的话可以在任意代码处看到宏的值是什么,修改起来比较方便。
           如果这些宏通常只要你在自己的模块中调试时所用,并且不想改动任何其他人的头文件时,也是不错的办法。

        b) 定义在.h/.hxx/.hpp头文件内
           这个就不多说了,被全局引用的宏定义。通常是一些Feature开关的设定。比如:
            // xxx_global_features.h
           #define __PC_SIDE_DEBUG__
           #if defined(__ON_HARDWARE__)
           #define __PC_SIDE_DEBUG__
           #endif // #if defined(__ON_HARDWARE__)

    3. 简写
        所以要简写……
        // xxx.h
        #define XXX_CONTRACT_FLAG_TAG_MUST_BE_PRESENT   (0×0000 << 0)
        #define XXX_CONTRACT_FLAG_TAG_MUST_BE_PAIRED   (0×0000 << 1)
        // xxx.c
        #include “xxx.h”
        #define __CF_PRST XXX_CONTRACT_FLAG_TAG_MUST_BE_PRESENT
        #define __CF_PAIR XXX_CONTRACT_FLAG_TAG_MUST_BE_PAIRED
        {
             // …
              xxx_flag = __CF_PRST|__CF_PAIR;
        }

         如果这招从来没用过……别跟我讲你是用C语言写程序的……。

    3. 代码和静态数据分离
        先声明,我以确在别人的源代码中见过这种用法,不过在我看到之前我本人就已经在用了…… 不能说是我发明的,但的确是英雄所见。
        举个例子,比如我们有一个算法库,里面有定义很多很多很多数组,在一个特殊的OS A(Nucleus)上出于加速及优化方法考虑,一定要定义为静态数组。但同时,由于我要移植到另一个特别OS B上(如Symbian),不允许这样使用。因此,我们要把所有代码里所有的静态数组都找出来,再在栈上定义它们。
       这样的话,我们将有两份或多份代码来同时维护,如:
       // my_algo.c for OS A
       static uint32_t __some_global_datas[] = {
            0, 1, 2, 3, 4, 5 ….
            0, 1, 2, 3, 4, 5 ….
             ….
      };
       // my_algo.c for OS B
       {
           uint32_t __some_global_datas[] = {
                0, 1, 2, 3, 4, 5 ….
                0, 1, 2, 3, 4, 5 ….
               ….
           };
       }

        有一个办法可以只通过一个宏,将两份代码合并到一起,而且不会使代码冗长,最可能不是最好的办法。
        1) 先定义一个OS Feature的头文件:
        // which_os.h
        //#define __OS_A__
         #define __OS_B__

        2) 再定义一个专门用来存数组数据的头文件:
         // array_datas.h
        #if defined(__MY_ALGO_C__)
                 0, 1, 2, 3, 4, 5 ….
                0, 1, 2, 3, 4, 5 ….
               ….
        #endif // #if defined(__MY_ALGO_C__)

        是的你没有看错,的确在头文件里是这样写的,而且没有任何的包括守护。

        3) 再来看看新的my_algo.c
        #define __MY_ALGO_C__
        #include “which_os.h”
        #if defined(__OS_A__)
         static uint32_t __some_global_datas[] = {
             #include “array_datas.h”
        };
       #endif // #if defined(__OS_A__)
       // ….
        {
              #if defined(__OS_B__)
               uint32_t __some_global_datas[] = {
                  #include “array_datas.h”
               };
              #endif // #if defined(__OS_B__)
       }

       想想有多份代码的话,有一天老板告诉你说,“我们要真对那些数据的内容做一次修改,然后要对每份代码进行同步。”,如果你真的不知道这种Hack技巧,OH MY GOD,我想我真的要疯掉了。
        如果你跟我一样,在代码中的确需要这样的功能,有这样的特殊需求,那么可以试一下。
       适应不同的OS的同时,代码整洁不了,不是吗?

    4. 数据填充模板
       看过之上几招之后,是不是感觉很用法怪异,作者很BT?那我再来告诉你招巨BT,但不太实用的Hack技巧吧。
       打个比方,我有n个固定的UI Widget的样式数据,像下面这样写在头文件如:
       // x,   y,   z,   w,     h, ext
             0,   0, 0, 200, 20, 0×11, // Icon bar
             0,   200, 0, 200, 20, 0×22, // Soft bar
            ….

       而我要用到这些数据的结构体一共有2个,被定义如下:
      struct __struct_UI_widget_coord {
              uint32_t x;
              uint32_t y;
        };
       struct __struct_UI_widget_ext {
              uint8_t ext;
        };
       static struct __struct_UI_widget_coord   __global_UI_coords[TOTAL_UI_WIDGETS];
       static struct __struct_UI_UI_widget_ext __global_UI_exts[TOTAL_UI_WIDGETS];

       怎样将我的UI_widget_detail.h中的数据分别填进两个数据中?看看下面的方法:
       1) 修改UI_widget_detail.h为如下的格式:
       // UI_widget_detail.h
       // x,   y,   z,   w,     h, ext
            __FILL_DATA( 0,   0, 0, 200, 20, 0×11 ),// Icon bar
            __FILL_DATA( 0,   200, 0, 200, 20, 0×22 ),// Soft bar
            ….
       
        2) 填充数组数据
       #undef __FILL_DATA
       #define __FILL_DATA( x, y, z, w, h, ext )   { x, y }
        static struct __struct_UI_widget_coord   __global_UI_coords[TOTAL_UI_WIDGETS] = {
            #include “UI_widget_detail.h”
       };
       #undef __FILL_DATA
       #define __FILL_DATA( x, y, z, w, h, ext )   { ext }
       static struct __struct_UI_UI_widget_ext __global_UI_exts[TOTAL_UI_WIDGETS] = {
            #include “UI_widget_detail.h”
       };

    5. 代码复用模板
       我想你一定认为我疯了,不过这些大多不为人知或不常用的技巧的的确确,我一直在用。
        不敢说如火纯青,大概也让我少敲击了几下键盘。来看看代码复用模板是怎么一回事?
       还是以举个例子:
             比如我有一个LIST数据结构,让我感觉烦的是,每次做迭代操作都要写一大堆代码,而且都是一样的。
             像下面这个样子:
              {
                    lnode_ptr_t curr;
                    lnode_ptr_t next;
                    // …
                    curr = some_first_node;
                    while ( NULL != curr ) {
                        next = curr->_next;
                        operate1( curr );
                        operate2( curr );
                        curr = next;
                   }
                   // …
             }

             如果这样的代码在几百个.c中到处被用到,而且只有中间操作过程不一样,会不会感觉很烦呀?
            
             那我来给你个提示,怎么复用这些框架代码:
             #define FOREACH_NODE_FORWARD( __first, __curr, __op ) \
                          do { \
                                 lnode_ptr_t curr__ = (__first); \
                                while ( NULL != curr__ ) \
                                { \
                                      (__curr) = curr__; \
                                      curr__ = curr__->_next; \
                                      __op \
                                } \
                          } while ( 0 );

               使用方式如下:
               {
                    lnode_ptr_t curr_ptr = NULL;
                   // …
                   FOREACH_NODE_FORWARD
                   (
                         some_first_node,
                         curr_ptr,
                         {
                             operate1( curr );
                             operate2( curr );
                         }
                    );
                   // …
               }

              看懂了吗?我们真的用宏把这些框架代码给复用了,而且使代码很整洁美观。我们并没有增加一个变量就能使结构如此的完美。

zz from www.donevii.com 或 www.cpplite.com

===========================================

Part I:不定變數

幾個學校課程不太會講到的技巧,第一個是不定變數的使用:

    #ifdef DEBUG
    #define debug_printf(str, ...)     do {         printf(str, __VA_ARGS__);     } while (0)
    #else
    #define debug_printf(str, ...)
    #endif

關鍵是 __VA_ARGS__,這樣可以很愉快的使用 debug_printf()。

這個 macro 在遇到
debug_printf("no params");
這樣的用法時,會展開成
printf("no params",);
(注意逗號),然後就出問題了。

GNU cpp 和 C99 都有對應解法,gnu cpp info 的 Variadic Macros 這一節有說明。


Part II:將傳入的參數變成字串

另外一個技巧是將傳入的參數變成字串:

    #define print_var(var)     do {         printf("%s: %s\n", #var, var);     } while (0)

關鍵字是 #var。當輸入 print_var(argv[0]); 時就會把以上的 Macro 展開為 printf("%s: %s\n", "argv[0]", argv[0]);。


Part III:將傳入的參數名稱變化

假設你想要把傳入參數名稱再變化,用 ## 穿插其中:

    #define print_three_var(var)
         do {
             print_var(var);
             print_var(var##2);
             print_var(var##3);
         } while (0)


因為你不能用 var2 來表示 var + "2",所以你必須用 ##。當你傳入 print_three_var(telephone) 時,他會展開成:

    print_var(telephone);
    print_var(telephone2);
    print_var(telephone3);

補充一下:這邊的 print_var() 是 Part II 裡的 print_var()。

zz from somewhere


http://www.ppmy.cn/news/674041.html

相关文章

C语言宏的主要作用

宏主要有四大类作用&#xff1a; 1.和条件编译指令配合&#xff0c;进行条件编译. 比如: 1)头文件的防止多重包含机制. 2)根据不同编译器选择不同代码段. 2.对需要变动的常量进行控制. 比如: 控制定义数组的长度. 3.对应码(比如寄存器功能码、状态码、指令的参数码、一些协议的…

c语言的 宏

一、常规宏定义 在 C 语言中&#xff0c;可以采用命令 #define 来定义宏。该命令允许把一个名称指定成任何所需的文本&#xff0c;例如一个常量值或者一条语句。在定义了宏之后&#xff0c;无论宏名称出现在源代码的何处&#xff0c;预处理器都会把它用定义时指定的文本替换掉…

C语言控制和鼠标键盘 (windows环境)

C语言控制鼠标点击以及键盘输入 使用场景主要的几个函数介绍实例键盘输入对应值 使用场景 当需要实现一些重复的点击&#xff0c;或者自动输入的情况下&#xff0c;可以使用到类似的功能&#xff0c;例如网页的自动刷新&#xff0c;QQ的自动输入&#xff0c;&#xff08;嘴炮连…

c语言中的宏的妙用

文章背景 前段时间&#xff0c;为了做一个项目中的程序&#xff0c;大致是实现SQL类似功能的索引功能&#xff0c;需要建立一张索引表&#xff0c;分析后&#xff0c;该表是在程序运行前就存在的&#xff0c;在参考优秀代码后&#xff0c;自己还是决定用宏来写这样一个功能的实…

【C语言进阶】宏

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;> c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是…

Centos开放端口以及查看端口和防火墙配置命令

查看防火墙某个端口是否开放 firewall-cmd --query-port8080/tcp 开放防火墙端口 8080 firewall-cmd --add-port8080/tcp --permanent 开启端口后需执行 firewall-cmd --reload 使其生效 重新加载防火墙规则 firewall-cmd --reload 关闭防火墙端口 firewall-cmd --remove-port8…

智慧园区平台建设解决方案

智慧园区是指利用现代互联网物联网技术&#xff0c;对园区内的设施、设备和人员进行高效管理和智能化运营的一种模式。越来越多的城市开始致力于发展智慧园区&#xff0c;实现园区内的资源共享和高效利用。为了让智慧园区达到最佳的效果&#xff0c;我们需要从平台建设方面入手…

gta5怎么设置画质最好_GTA5:如何让你的游戏画质更好,游戏更顺手,一波设置教给大家!...

GTA5&#xff1a;如何让你的游戏画质更好&#xff0c;游戏更顺手&#xff0c;一波设置教给大家&#xff01; 大家都知道GTA5这个游戏&#xff0c;这个游戏无疑最受玩家瞩目热门游戏之一&#xff0c;但是因为这款游戏对配置的要求比较高&#xff0c;如果想达到完美的平衡以及游戏…