代码之家  ›  专栏  ›  技术社区  ›  John Rudy

α-预处理器运算符和GOTCHAS的应用是什么?

  •  86
  • John Rudy  · 技术社区  · 16 年前

    ## 预处理器操作员。据K&R:

    预处理器操作符 提供一种连接实际值的方法 宏扩展期间的参数。如果 替换文本中的参数为 毗邻 ## ## 和周围的空白是 删除,并重新扫描结果。 例如,宏 paste 连接其两个参数:

    #define paste(front, back) front ## back

    paste(name, 1) 创建令牌 name1

    在现实世界中,人们如何以及为什么要使用它?有什么实用的例子,有什么要考虑的吗?

    13 回复  |  直到 9 年前
        1
  •  50
  •   Michael Burr    16 年前

    使用令牌粘贴时需要注意一件事: ## # “)预处理运算符是指必须使用额外级别的间接寻址,才能在所有情况下正常工作。

    如果不这样做,并且传递给令牌粘贴操作符的项本身就是宏,那么您将得到可能不是您想要的结果:

    #include <stdio.h>
    
    #define STRINGIFY2( x) #x
    #define STRINGIFY(x) STRINGIFY2(x)
    #define PASTE2( a, b) a##b
    #define PASTE( a, b) PASTE2( a, b)
    
    #define BAD_PASTE(x,y) x##y
    #define BAD_STRINGIFY(x) #x
    
    #define SOME_MACRO function_name
    
    int main() 
    {
        printf( "buggy results:\n");
        printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
        printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
        printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
    
        printf( "\n" "desired result:\n");
        printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
    }
    

    输出:

    buggy results:
    SOME_MACRO__LINE__
    BAD_PASTE( SOME_MACRO, __LINE__)
    PASTE( SOME_MACRO, __LINE__)
    
    desired result:
    function_name21
    
        2
  •  47
  •   Peter Mortensen Len Greski    9 年前

    #define WIDEN2(x) L ## x
    #define WIDEN(x) WIDEN2(x)
    //Note you need a WIDEN2 so that __DATE__ will evaluate first.
    

    在这里,他们希望使用两个字节的字符串,而不是每个字符字符串使用一个字节。这看起来似乎真的毫无意义,但他们这么做有很好的理由。

     std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
    

    他们将其与另一个宏一起使用,该宏返回带有日期和时间的字符串。

    L __ DATE __ 将给您一个编译错误。


    Windows:对通用Unicode或多字节字符串使用##

    Windows使用类似以下内容:

    #ifdef  _UNICODE
        #define _T(x)      L ## x
    #else
        #define _T(x) x
    #endif
    

    _T 在代码中到处都使用


    各种库,用于清除访问器和修改器名称:

    我还看到它在代码中用于定义访问器和修饰符:

    #define MYLIB_ACCESSOR(name) (Get##name)
    #define MYLIB_MODIFIER(name) (Set##name)
    

    同样,您也可以将此方法用于任何其他类型的智能名称创建。


    #define CREATE_3_VARS(name) name##1, name##2, name##3
    int CREATE_3_VARS(myInts);
    myInts1 = 13;
    myInts2 = 19;
    myInts3 = 77;
    
        3
  •  14
  •   bk1e    16 年前

    下面是我升级到新版本编译器时遇到的一个问题:

    不必要地使用令牌粘贴操作符( ## )不可移植,可能会生成不需要的空白、警告或错误。

    例如,可以尝试在编译时使用标记粘贴运算符构建字符串文字:

    #define STRINGIFY(x) #x
    #define PLUS(a, b) STRINGIFY(a##+##b)
    #define NS(a, b) STRINGIFY(a##::##b)
    printf("%s %s\n", PLUS(1,2), NS(std,vector));
    

    在某些编译器上,这将输出预期结果:

    1+2 std::vector
    

    在其他编译器上,这将包括不需要的空白:

    1 + 2 std :: vector
    

    相当现代的GCC版本(>=3.3左右)将无法编译以下代码:

    foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token
    foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token
    foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token
    foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token
    

    解决方案是在将预处理器令牌连接到C/C++运算符时忽略令牌粘贴运算符:

    #define STRINGIFY(x) #x
    #define PLUS(a, b) STRINGIFY(a+b)
    #define NS(a, b) STRINGIFY(a::b)
    printf("%s %s\n", PLUS(1,2), NS(std,vector));
    

    GCC CPP documentation chapter on concatenation 包含有关令牌粘贴运算符的更多有用信息。

        4
  •  6
  •   Vebjorn Ljosa    16 年前

    这在各种情况下都很有用,以避免不必要的重复。下面是来自Emacs源代码的示例。我们想从库中加载一些函数。函数“foo”应分配给 fn_foo 等等我们定义以下宏:

    #define LOAD_IMGLIB_FN(lib,func) {                                      \
        fn_##func = (void *) GetProcAddress (lib, #func);                   \
        if (!fn_##func) return 0;                                           \
      }
    

    然后我们可以使用它:

    LOAD_IMGLIB_FN (library, XpmFreeAttributes);
    LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
    LOAD_IMGLIB_FN (library, XpmReadFileToImage);
    LOAD_IMGLIB_FN (library, XImageFree);
    

    好处是不必同时编写这两个文档 fn_XpmFreeAttributes "XpmFreeAttributes" (并冒着拼写错误的风险)。

        5
  •  4
  •   Community Neeleshkumar S    7 年前

    关于StackOverflow的前一个问题要求使用一种平滑的方法为枚举常量生成字符串表示,而不需要大量容易出错的重新键入。

    Link

    ENUM_BEGIN( Color )
      ENUM(RED),
      ENUM(GREEN),
      ENUM(BLUE)
    ENUM_END( Color )
    

    const char *ColorStringTable[] =
    {
      "RED",
      "GREEN",
      "BLUE"
    };
    

    字符串表的名称来自使用##运算符将宏参数(即颜色)粘贴到StringTable。像这样的应用程序(技巧?)是#和##运算符非常宝贵的地方。

        6
  •  3
  •   Peter Mortensen Len Greski    9 年前

    当需要将宏参数与其他参数连接在一起时,可以使用标记粘贴。

    它可用于模板:

    #define LINKED_LIST(A) struct list##_##A {\
    A value; \
    struct list##_##A *next; \
    };
    

    struct list_int {
    int value;
    struct list_int *next;
    };
    

        7
  •  2
  •   Tall Jeff    16 年前

    我在C程序中使用它来帮助正确地执行一组方法的原型,这些方法必须符合某种调用约定。在某种程度上,这可以用于穷人在直线C中的对象定向:

    SCREEN_HANDLER( activeCall )
    

    扩展到如下内容:

    STATUS activeCall_constructor( HANDLE *pInst )
    STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
    STATUS activeCall_destructor( HANDLE *pInst );
    

    当您执行以下操作时,这将对所有“派生”对象强制执行正确的参数化:

    SCREEN_HANDLER( activeCall )
    SCREEN_HANDLER( ringingCall )
    SCREEN_HANDLER( heldCall )
    

        8
  •  2
  •   Jack Jack    16 年前
        9
  •  2
  •   c0m4    16 年前

    
    
    #define ASSERT(exp) if(!(exp)){ \
                          print_to_rs232("Assert failed: " ## #exp );\
                          while(1){} //Let the watchdog kill us 
    
    
    
        10
  •  1
  •   John Millikin    16 年前

    我使用它为宏定义的变量添加自定义前缀。比如:

    UNITTEST(test_name)
    

    扩展到:

    void __testframework_test_name ()
    
        11
  •  1
  •   mcherm    16 年前

    主要用途是当您有命名约定并且希望宏利用该命名约定时。也许您有几个方法系列:image\u create()、image\u activate()和image\u release()以及file\u create()、file\u activate()、file\u release()和mobile\u create()、mobile\u activate()和mobile\u release()。

    #define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())
    

    当然,一种“对象的最小版本”并不是唯一适用的命名约定——几乎绝大多数命名约定都使用公共子字符串来形成名称。它可以告诉我函数名(如上所述),或者字段名、变量名,或者其他任何东西。

        12
  •  1
  •   Keshava GN    11 年前

    WinCE的一个重要用途是:

    #define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))
    

    #define ADDR_LEFTSHIFT                          0
    
    #define ADDR_WIDTH                              7
    

    BITFMASK(ADDR)
    
        13
  •  0
  •   ya23 devoured elysium    16 年前

    它对于日志记录非常有用。你可以做:

    #define LOG(msg) log_msg(__function__, ## msg)
    

    或者,如果编译器不支持 作用 :

    #define LOG(msg) log_msg(__file__, __line__, ## msg)
    

    我的C++语法可能不太正确。