【问题标题】:How to use #ifndef with macro argument?如何使用带有宏参数的#ifndef?
【发布时间】:2018-06-25 22:23:29
【问题描述】:

我在 header.h 中有一个简单的代码-

#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;} 

这个 header.h 包含在 code.c 文件中,但我的要求是 - 我希望先检查 SWAP,例如-

#ifndef SWAP(a, b)
#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
#endif

这是正确的还是我不必在第一行提供参数?

【问题讨论】:

  • 改用::std::swap
  • SWAP(a, a) 使用此定义失败。
  • 如果您说这是在 C 文件中使用,为什么要将您的问题标记为 C++?
  • @hvd:C 源文件和 C++ 源文件都获得#included 的头文件并不罕见。
  • 1) 永远不要使用 XOR 交换,它们是危险的、不可读的并且很可能不会更快。 2)使用函数而不是类似函数的宏。 3) 在编写类似函数的宏时(应该避免),通过用括号括起来来保护所有宏参数免受意外优先级问题的影响,如初学者级别的 C 编程书籍中所示。

标签: c++ c c++11 macros


【解决方案1】:

不正确。

#ifndef的正确用法是:

#ifndef SWAP
#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
#endif

来自http://en.cppreference.com/w/cpp/preprocessor/conditional

#ifndef identifier

【讨论】:

    【解决方案2】:

    你想编码

    #ifndef SWAP
    #define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
    #endif
    

    #ifndef 只是查看预处理器的符号表(查看某些给定的预处理器符号是否存在)。它不关心您的 SWAP 宏的参数。

    当然,在真正的 C++11 中,您应该更喜欢标准的 std::swap 函数。它类型安全、可读性更强、更安全(将 SWAP(t[i++],i) 视为行为不端的示例)。

    【讨论】:

    • 公平地说,std::swap(t[i++],i) 是未定义的行为(假设 iint)直到 C++17,仍然有多个可能的明确定义的结果。当然,你的观点仍然成立。 SWAP(t[i], i) 不会很顺利。
    • @chris 好点,swap(t[i++], t[last]) 会是一个更好的例子(或者只是与i 无关的任何第二个参数)。
    【解决方案3】:
    #ifndef SWAP
    #define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}
    #endif
    

    以上更改适用于 c,c++11。可能它适用于所有版本的 c++XX。

    【讨论】:

    • 是的,它可以工作,但它不是特别安全......如果(a==b) 怎么办?如果(a &| b) 是指针或表达式……等等。
    【解决方案4】:

    在提出问题之前,您可以查看标准以了解其使用方式。

    来自标准 - §6.10.1p5

    表单的预处理指令

    # ifdef identifier new-line groupopt
    # ifndef identifier new-line groupopt
    

    检查标识符当前是否定义为 宏名称。它们的条件分别相当于#if defined identifier 和#if !defined identifier。

    如果你不确定宏名称是什么,参数是什么等等,那么再说一遍。

    来自标准§6.10.3.p10

    表单的预处理指令

    # define identifier lparen identifier-listopt ) replacement-list new-line
    # define identifier lparen ... ) replacement-list new-line
    # define identifier lparen identifier-list , ... ) replacement-list new-line
    

    定义了一个带参数的类函数宏,用法类似 语法上是函数调用。参数由 标识符的可选列表,其范围从它们的 标识符列表中的声明,直到换行符 终止#define 预处理指令。 随后的每一个 类似函数的宏名称的实例,后跟 ( 作为下一个 preprocessing token 引入了预处理token的序列 被定义中的替换列表替换(一个 调用宏)。...

    最后一部分只是让您知道应该在ifndef 中写什么来代替标识符。从突出显示的部分可以清楚地看出。

    C 中,您必须做大量工作才能编写通用且正确的交换宏。对于两个相同的值,您的 swap 将无法按预期工作。

    【讨论】:

      【解决方案5】:

      在较早的 C++ 时代,在使用 new & deletenew [] & delete [] 出现更安全的智能指针 std::shared_ptr<T>std::unique_ptr<T> 之前,这些是我曾经在 C++ 中用于帮助管理内存的一些较旧的宏。

      #ifndef SAFE_DELETE
          #define SAFE_DELETE(p)  { if(p) { delete (p); (p) = nullptr; } }
      #endif
      
      #ifndef SAFE_DELETE_ARRAY
          #define SAFE_DELETE_ARRAY(p)    { if(p) { delete [] (p); (p) = nullptr; } }
      #endif 
      

      类似于您要实现的目标:这些类型的宏的设计通常会查看 tag - identifier 是否尚未定义。如果不是,则定义名称及其参数和功能,然后结束 if 定义。另外,在使用宏时,它可能看起来有点过头了,但始终建议将每个参数名称括在括号中。所以在你的情况下,你会想要这样的东西:

      #ifndef SWAP
          #define SWAP(a, b)  if ( (a) != (b) ) { \
                                  ((a) ^= (b));   \
                                  ((b) ^= (a));   \
                                  ((a) ^= (b));   \
                              } else {            \ 
                                  ((a) = (a));    \
                                  ((b) = (b));    \
                              }                   
      #endif
      

      现在上面的宏很简单,它不能防止无效类型,如指针、表达式、引用等,但它应该适用于任何内置的默认基本类型。

      【讨论】:

        【解决方案6】:

        为了简要回答您的问题,就像其他答案已经说明的那样。 #ifdef/#ifndef 条件只关心宏标识符,因此参数不是其语法的一部分。

        但是,您的宏有几个需要解决的弱点。首先,请注意,使用 XOR 运算符进行交换,虽然是避免使用临时变量的常用技巧,但如果两个参数是同一个对象,则会失败。这是因为第一个 XOR 的结果会是 0,剩下的 XOR 步骤无法恢复原始值。其次,这个版本的宏不能用于指针类型,因为 XOR 需要整数类型。第三,宏多次调用参数,如果参数是一个有副作用的表达式,就会出现问题。第四,宏中的复合语句应由do .. while (0) 保护,以允许宏扩展为语句。这使得宏在语法上更清晰,因此它后面的分号不是虚假的。

        如单独答案中所述,在 C++ 中,使用 std::swap 而不是自己定义。不幸的是,C 没有提供通用的交换效用函数。但是,编写一个泛型函数并不难:

        static inline void swap_generic (void *a, void *b, void *t, size_t sz) {
            if (a != b) {
                memcpy(t, a, sz);
                memcpy(a, b, sz);
                memcpy(b, t, sz);
            }
        }
        

        然后您的宏可以调用此函数。

        #ifndef SWAP
        # ifdef __cplusplus
        #  define SWAP(a, b) std::swap(a, b)
        # else
        #  define SWAP_ASSERT(X) _Static_assert(X, #X)
        #  if  __STDC_VERSION__ < 201112L
        #   undef SWAP_ASSERT
        #   define SWAP_ASSERT(X) struct dummy
        #  endif
        #  define SWAP(a, b) do {                                \
               SWAP_ASSERT(sizeof(a) == sizeof(b));              \
               char t[sizeof(a) != sizeof(b) ? -1 : sizeof(a)];  \
               swap_generic(&(a), &(b), t, sizeof(a));           \
           } while (0)
        # endif
        #endif
        

        如果检测到 C++,请注意我们如何使用 std::swap

        如果您使用支持 typeof 扩展的 C 编译器,那么宏可以大大简化,因为您不需要通用交换函数。

        #ifndef SWAP
        # ifdef __cplusplus
        #  define SWAP(a, b) std::swap(a, b)
        # else
        #  define SWAP(a, b) do {                      \
               typeof(a) *p = &(a), *q = &(b), t = *p; \
               *p = *q;                                \
               *q = t;                                 \
           } while (0)
        # endif
        #endif
        

        请注意,typeof 版本提高了类型安全性。

        【讨论】:

        • 哇,我真的很喜欢你的宏设计!看起来很方便;但是为什么要经历这些麻烦呢?你不能用一个简单的就地 lambda 实现同样的效果吗? -修辞问题- ...
        • 我知道 C 缺少 lambdas...自从我用 C 编写任何东西可能已经有 20 年了...我不记得了,但我认为 C 在宏也是,...我知道 C 没有模板,也没有只有结构的类...并且一些相同的关键词在 C 中意味着一件事,但在 C++ 中意味着不同。它可能不是宏,它可能是我正在考虑的命名空间......
        • @FrancisCugler:我不清楚你的意思是什么,但是我提出的宏的复杂性是为了解决我在 C 源代码中使用宏时提出的 4 个问题,并使用std:swap 当检测到 C++ 时。
        • 我的道歉;我开始失去思路,因为天色已晚,我将在接下来的一个小时内完成这里。我的语法 - 现在打字很糟糕 - 可以说 - 半开玩笑......
        • 我最初的陈述是基于我通常不使用 C 编写并且我主要使用 C++...
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-12
        相关资源
        最近更新 更多