【问题标题】:What happens if you declare a variable inside a macro?如果在宏中声明变量会发生什么?
【发布时间】:2011-07-06 08:49:23
【问题描述】:

假设我有一个这样定义的宏:

#define FOO(x,y) \ 做 { 整数a,b; a = f(x); b = g(x); y = a+b; } 而 (0)

在扩展宏时,GCC 是否“保证”a、b 的任何唯一性?我的意思是,如果我以下列方式使用 FOO:

整数 a = 1, b = 2; FOO(a,b);

之后,预处理这将是:

整数 a = 1, b = 2; 做 { 整数a,b; a = f(a); b = g(b); b = a+b; } 而 (0)

编译器能否/将区分 do{} 外的 a 和 do 内的 a 吗?我可以使用什么技巧来保证任何类型的唯一性(除了让里面的变量有一个乱码,这样其他人就不太可能使用相同的名字)?

(理想情况下,函数会更有用,但我的特殊情况不允许这样做)

【问题讨论】:

  • 不要将宏用于此类事情。我们有功能是有原因的。
  • @jleedev:卫生宏听起来是个好主意……为什么 gcc 没有实现这个?听起来实现起来很简单直接。 @ThiefMaster:同意函数将是理想的,但它们是不可能的,因为我不能为此添加公共函数。
  • 您无法编写完全等效的函数,但您可以编写一个返回 a+b 的函数,而不是将其存储在 y 参数中。为什么你的情况不允许呢?
  • @R.D. gcc 是一个 C 编译器,实现了 C 标准指定的内容。它还实现了一些扩展,但这些扩展鼓励人们编写不可移植的代码;应该避免它们。

标签: c++ c


【解决方案1】:

如果我们考虑变量的作用域,可以保证 do..while() 中的 a,b 与外部定义的不同。

对于您的情况,在 do..while() 中将不存在外部定义的 a,b。

使用 MACRO 时需要注意很多事项。

【讨论】:

    【解决方案2】:

    宏只执行字符串替换。语义低,编译器对预处理器的了解有限(本质上是#pragma,实际上不是预处理器关键字和源代码行信息)。

    在您的情况下, a 和 b 未初始化本地值。行为是不可预测的。 您的扩展代码等同于以下代码。

    int a = 1, b = 2;
    do {
      int a___,b___;
      a___ = f(a___);
      b___ = g(b___);
      b___ = a___+b___;
    } while (0)
    

    为了避免在 c++ 中出现这种情况,最好使用内联函数或模板。 如果您使用符合 c 1999 的编译器,则可以在 c 语言中使用内联。 http://en.wikipedia.org/wiki/Inline_function

    在 c 中,您可以通过 () 定义更长的变量和周围的参数来制作更安全的宏:

    #define FOO(x,y) \
    do {
      int FOO__a,FOO__b;
      FOO__a = f(x);
      FOO__b = g(x);
      y = FOO__a+FOO__b + (y)*(y);
    } while (0)
    

    注意:我更改了您的示例,添加了 (y)*(y) 来说明情况

    最好只使用一次宏参数。 这可以防止这样的副作用:

    #define max(a,b) a>b?a:b
    max(i++,--y)
    

    Max 不会返回你想要的。

    【讨论】:

    • 在 C 中,创建一个(内联)函数。永远不要使用类似函数的宏。
    • 但宏在需要条件编译时很有用。调用内联函数并评估其参数,这可能毫无意义或性能下降。从不,建议您不必在不同环境之间对重要系统进行真实世界的移植。
    • @Rob11311 调用内联函数并导致性能问题表明您没有使用正确的编译器标志。
    【解决方案3】:

    变量 a 和 b 被视为局部范围内的任何局部变量。

    C 语言保证,如果这些变量恰好与外部范围变量同名,则局部变量将是更新的变量。

    这里有一个例子来说明:

    #include <stdio.h>
    
    #define FOO(x) \
    {              \
      int a;       \
      a = x;       \
      printf("%d\n", a); \
    }
    
    
    int main()
    {
      int a = 1;
    
      {
        int a = 2;
    
        printf("%d\n", a); // 2
    
        FOO(3); // 3
    
        printf("%d\n", a); // 2
      }
    
      printf("%d\n", a); // 1
    
      getchar();
    }
    

    现在,将程序中的每个变量都命名为“a”当然是个好主意,因为 C 保证局部变量优先。但从技术上讲,没有什么能阻止你。

    顺便说一句,MISRA-C 禁止这样命名,出于可读性和维护原因,它要求每个变量无论范围如何都具有唯一的名称。

    (作为旁注,类似函数的宏是非常糟糕的编程风格,不应该使用。改用真正的函数,如果性能很关键,则内联它们。)

    【讨论】:

      【解决方案4】:

      除了乱码没有任何技巧。 C 和 C++ 预处理器没有等效的 lisp gensym 或 hygienic 宏。

      【讨论】:

        【解决方案5】:

        不,不能保证唯一性。

        事实上,您的代码即将失败。

        宏就像替换文本。

        如果我在宏中,我通常会使用疯狂的变量名,如下所示:

        #define FOO(x,y) \
        do {
          int FOO_MACRO_a, FOO_MACRO_b;
          FOO_MACRO_a = f(x);
          FOO_MACRO_b = g(x);
          y = FOO_MACRO_a + FOO_MACRO_b;
        } while (0)
        

        【讨论】:

        • 你知道 C 中的命名空间规则是如何工作的吗?如果您声明了一个局部变量 X 并且碰巧在另一个范围内有一个也名为 X 的变量,那么您的局部变量将是被更改的那个。绝对没有必要混淆局部变量的命名。所以代码不会失败,命名局部变量与外部范围变量不同的唯一原因是可读性。
        • @Lundin:作用域和命名空间是分开的,请不要混淆术语。
        • @Chistopher 嗯……是的,不是的。 “范围”是标准中使用的语言书呆子术语,而“命名空间”是普通程序员在谈论通用级别(与语言无关)编程时使用的术语。在 C 语言中,标准根本没有提到命名空间,所以对于 C 程序员来说,命名空间和作用域是一回事。在 C++ 中,它们确实是完全不同的东西,不应混淆。
        • @Lundin 当 x 恰好是外部作用域中的 a 时,将会失败的是 a = f(x);。将其扩展到 a = f(a); 肯定会失败。
        【解决方案6】:

        如果您的目标是 gcc 和/或 g++,那么您可以使用它们的特殊宏块功能:

        #define max(x, y) ({ typeof(x) a_ = (x); \
                             typeof(y) b_ = (y); \
                             (a_ > b_) ? a_ : b_ })
        

        这允许您创建唯一的局部变量,与编写函数非常相似。

        当然,为了便携性,不推荐。另一方面,如果您只打算在提供 gcc/g++ 的系统上工作,那么它可以在所有这些系统上工作。

        来源:http://gcc.gnu.org/onlinedocs/gcc-3.0.1/cpp_3.html#SEC30

        此外,通过 gcc / g++,您可以使用 -Wshadow 命令行选项。如果您无意中重用了同名的局部变量,它会警告您。您可以进一步使用-Werror 来转换这些错误警告。现在,如果存在混淆变量的可能性,您将无法编译。不过,您需要确保使用块。其他人提出的 do/while() 就可以完成这项工作。

        int a;
        
        // code from macro;
        do { int a = 5; ... } while(false);
        

        使用我刚刚描述的组合 (-Wshadow + -Werror),当您执行 int a = 5 时会出现错误。

        【讨论】:

        • 不需要do while 你可以直接做some code {some code in block} some more code而不用do while。
        • @TomerWolberg 在这里查看选择的答案stackoverflow.com/questions/1067226/… — 这不是总是必需的。
        【解决方案7】:

        您实际上可以通过执行以下操作来确保无论 x 和 y 是什么,您都不会遇到此问题:

        #define FOO(x,y) \
        do\
        {\
          int x##y##a,x##y##b;\
          x##y##a = f(x);\
          x##y##b = g(x);\
          y = x##y##a + x##y##b;\
        } while (0)
        

        通过确保 a 和 b 名称包含 x 和 y 名称,您知道它们是不同的。但这是一个非常糟糕的代码,您可能不应该这样编写代码。

        【讨论】:

        • 有趣的建议!然而,仍然存在 2 个潜在问题:与原来的不同,这个新宏不能与表达式一起用作第一个参数,您应该删除宏末尾的 ;
        • @chqrlie 你是对的,我删除了;。而且恐怕第一个问题是不可能解决的。
        • 我想可以使用单个变量,即 2 个整数的数组,但我看不出有一种方法可以保证不会与 x 使用的任何内容发生冲突。将其命名为y##y 使其与y 不同,但x 可以是任何名称或表达式,因此可以包含任何标识符。我同意你的结论:OP 可能不应该这样编码。
        【解决方案8】:

        以文本方式按原样扩展,模参数替换,因此编译器无法提供您要求的那种保证——正如您在扩展中看到的那样, a 参数将引用内部的a,而不是外部的。解决方案确实是使用“乱码”名称,例如int FOO_a, FOO_b;

        【讨论】:

          【解决方案9】:

          在您的宏中,这确实是一种危险,x 的可能重用也是如此,它不能保证是左值,并且可能通过在宏中使用两次而改变。

          即使你确实需要一个宏,它仍然可以是一个内联函数的轻量级包装器,它可以接受 x 并为你提供 f(x)g(x) 而无需重新评估 x 会当然是安全的。

          在你的情况下是这样的:

          template< typename T >
          struct Foo
          {
             T& x;
          
             explicit Foo(T&x_) : x(x_)
             {
             }   
          
             int f();
             int g();
          };
          
          template<typename T>
          Foo<T> makeFoo(T& x)
          {
               return Foo<T>(x);
          }
          
          #define FOO(x,y)
          {
             Foo FOO_VAR(x);
             y = FOO_VAR.f() + FOO_VAR.g();
          }
          

          将是一种更安全的做事方式。当然,如果您根本不需要宏,请取消它。

          【讨论】:

            猜你喜欢
            • 2018-05-01
            • 1970-01-01
            • 1970-01-01
            • 2016-12-28
            • 2021-10-04
            • 1970-01-01
            • 2015-11-30
            • 2011-05-05
            • 1970-01-01
            相关资源
            最近更新 更多