如果在宏中声明变量会发生什么?

时间:2011-03-01 09:18:12

标签: c++ c

假设我有一个宏定义如下:

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

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

int a = 1, b = 2;
FOO(a,b);

之后,预处理将是:

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

编译器可以/将会区分do {}外的a和do内的a吗?我可以使用哪些技巧来保证任何类型的唯一性(除了使变量内部有一个乱码,这使得其他人不太可能使用相同的名称)?

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

9 个答案:

答案 0 :(得分:7)

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

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

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

答案 1 :(得分:6)

宏只执行字符串替换。语义很低,编译器对预处理器的了解有限(实际上#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不会返回你想要的东西。

答案 2 :(得分:3)

除了花边之外没有任何技巧。 C和C ++预处理器没有等效的lisp gensym或卫生宏。

答案 3 :(得分: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();
}

现在,当然,为您的程序中的每个变量命名可能是一个好主意&#34; a&#34;只是因为C保证局部变量优先。但从技术上讲,没有什么可以阻止你。

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

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

答案 4 :(得分:1)

不,不能保证唯一性。

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

宏就像替换文字一样。

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

#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)

答案 5 :(得分:1)

如果您要定位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时会出错。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

在您确实存在危险的宏中,x的可能重用可能不会保证为l值,并且可以通过在宏中使用两次来改变。

即使你确实需要一个宏,它仍然可以是内联函数周围的一个光包装器,并且它将使用x并为你提供f(x)g(x),而不必重新使用-evaluate 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();
}

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

答案 8 :(得分:0)

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

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

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