【问题标题】:Can a C macro contain temporary variables?C 宏可以包含临时变量吗?
【发布时间】:2012-01-06 21:26:27
【问题描述】:

我有一个需要宏化的函数。该函数包含临时变量,我不记得是否有任何关于在宏替换中使用临时变量的规则。

long fooAlloc(struct foo *f, long size)
{
   long      i1, i2;
   double   *data[7];

   /* do something */
   return 42;
}

宏表格:

#define ALLOC_FOO(f, size) \
{\
   long      i1, i2;\
   double   *data[7];\
\
   /* do something */ \
}

这样好吗? (即没有讨厌的副作用 - 除了通常的副作用:不是“类型安全”等)。顺便说一句,我知道“宏是邪恶的”——在这种情况下我只需要使用它——没有太多选择。

【问题讨论】:

  • 为什么需要宏化?如果您担心性能,那么(a)您可能会不必要地担心(除非您已经实际测量并得出结论认为这是一个重要的瓶颈)并且(b)您可以通过更多类型实现相同的效果- 安全的方式,通过创建函数inline(一些旧的或微软的编译器可能不支持这个)。
  • @KeithThompson 性能不是这里的问题。我正在编写一个扩展库,我需要一个包装函数来做一些数据编组。宏只是提供了一种自动生成大量“粘合”代码的不易出错的方式。
  • return 在宏内并没有像你想象的那样做。
  • @GregHewgill:我刚刚发现...我会相应地修改 sn-p。

标签: c c-preprocessor


【解决方案1】:

只有两种情况下,它才能以任何“合理”的方式发挥作用。

  1. 宏没有返回语句。您可以使用do while 技巧。

    #define macro(x) do { int y = x; func(&y); } while (0)
    
  2. 您只针对 GCC。

    #define min(x,y) ({ int _x = (x), _y = (y); _x < _y ? _x : _y; })
    

如果你解释一下为什么你必须使用宏(你的办公室有“宏星期一”之类的东西吗?),这会有所帮助。否则我们真的帮不上忙。

【讨论】:

  • 宏星期一和微星期五... :-) 话虽如此,我喜欢使用宏做一件事:用__FILE____func____LINE__ 构建对象。比每次都输入所有这些要实用得多。
  • @AlexisWilke:但如果代码已经作为函数存在,则不能使用其中任何一个。
  • 这就是我要创建宏的原因。我特别用它来记录日志。我创建了一个日志对象,它在销毁时将字符串写入文件/控制台/系统日志...所以我有一个类,并且每次都可以只是手动创建一个对象。使用宏,我不必一遍又一遍地输入所有这些信息。我的宏在这个log.h header 的最后。也可能是我们没有 OP 的全貌。
  • 我们没有来自 OP 的故事这一事实正是我要表达的观点。 OP 说“必须使用宏”......但没有任何解释为什么 OP 声称需要使用宏,在黑暗中的猜测不太可能让我们更接近理解 OP 试图用宏解决什么。跨度>
【解决方案2】:

C 宏只是(相对简单的)文本替换。

所以您可能要问的问题是:我可以像下面的示例那样在函数中创建块(也称为复合语句)吗?

void foo(void)
{
    int a = 42;
    {   
        int b = 42;
        {
            int c = 42; 
        } 
    }
}

答案是肯定的。

现在正如@DietrichEpp 在他的回答中提到的那样,如果宏是您的示例中的复合语句,那么用do { ... } while (0) 而不仅仅是{ ... } 将宏语句括起来是一个好习惯。下面的链接解释了宏中的do { ... } while (0) 试图阻止的情况:

http://gcc.gnu.org/onlinedocs/cpp/Swallowing-the-Semicolon.html

此外,当您编写类似函数的宏时,请始终问自己这样做是否有真正的优势,因为通常编写函数会更好。

【讨论】:

    【解决方案3】:

    首先,我强烈推荐内联函数。宏能做和不能做的事情很少,而且它们更有可能做您期望的事情。

    我在其他答案中没有看到的宏的一个缺陷是变量名的阴影。
    假设你定义了:

    #define A(x) { int temp = x*2; printf("%d\n", temp); }
    

    有人这样使用它:

    int temp = 3;
    A(temp);
    

    预处理后的代码为:

    int temp = 3;
    { int temp = temp*2; printf("%d\n", temp); }
    

    这不起作用,因为内部温度会影响外部温度。
    常见的解决方案是调用变量__temp,假设没有人会使用这个名称定义变量(这是一个奇怪的假设,因为您刚刚这样做了)。

    【讨论】:

    • 好吧,要让临时变量的名称异常独特,您可以为其生成一个 UUID :)
    【解决方案4】:

    这基本没问题,除了宏通常用do { ... } while(0) 括起来(请看this question 的解释):

    #define ALLOC_FOO(f, size) \
        do { \
            long      i1, i2;\
            double   *data[7];\
            /* do something */ \
        } while(0)
    

    此外,就您原来的 fooAlloc 函数返回 long 而言,您必须更改宏以以其他方式存储结果。或者,如果你使用 GCC,你可以试试compound statement 扩展:

    #define ALLOC_FOO(f, size) \
        ({ \
            long      i1, i2;\
            double   *data[7];\
            /* do something */ \
            result; \
        })
    

    最后,您应该注意扩展宏参数可能产生的副作用。通常的模式是为块内的每个参数定义一个临时变量并使用它们来代替:

    #define ALLOC_FOO(f, size) \
        ({ \
            typeof(f) _f = (f);\
            typeof(size) _size = (size);\
            long      i1, i2;\
            double   *data[7];\
            /* do something */ \
            result; \
        })
    

    【讨论】:

    • __f__size 不是保留的吗?
    • @Zaxter 严格来说,是的。
    【解决方案5】:

    Eldar 的回答向您展示了宏编程的大部分陷阱和一些有用(但非标准)的 gcc 扩展。

    如果您想遵守标准,宏(用于通用性)和inline 函数(用于局部变量)的组合会很有用。

    inline
    long fooAlloc(void *f, size_t size)
    {
       size_t      i1, i2;
       double   *data[7];
    
       /* do something */
       return 42;
    }
    
    
    #define ALLOC_FOO(T) fooAlloc(malloc(sizeof(T)), sizeof(T))
    

    在这种情况下,使用 sizeof 只会在编译时计算类型的表达式,而不是它的值,所以这不会计算 F 两次。

    顺便说一句,“尺寸”通常应使用size_t 输入,而不是long 或类似名称。

    编辑:关于乔纳森关于inline函数的问题,我已经写了一些关于C99的inline模型,here

    【讨论】:

    • 应该是static inline ...
    • @Jonathan,为什么会这样?
    • 您需要非常小心 C (IMO) 中的 inline。我在 SO 上找到的三个最相关的问题是 "Inline definitions?""Is inline without static or extern ever useful in C99?""extern inline?",没有任何特定的顺序。 (并且,郑重声明,我并不是在谈论这个问题;我是在问一个问题。)
    • @Jonathan,是的,没问题,我就是这么认为的,只是一个问题。请参阅我的编辑中的链接,了解我认为应该如何使用 C99 编写 inline 函数。
    • 在我阅读的所有方法中,这对我有用,谢谢!
    【解决方案6】:

    是的,当您使用块结构并且临时变量在该块的内部范围内声明时,它应该可以工作。

    注意 } 之后的最后一个 \ 是多余的。

    【讨论】:

      【解决方案7】:

      一个不完美的解决方案:(不适用于递归宏,例如彼此内部的多个循环)

      #define JOIN_(X,Y) X##Y
      #define JOIN(X,Y) JOIN_(X,Y)
      #define TMP JOIN(tmp,__LINE__)
      
      #define switch(x,y) int TMP = x; x=y;y=TMP
      
      int main(){
        int x = 5,y=6;
        switch(x,y);
        switch(x,y);
      }
      

      运行预处理器后会变成:

      int main(){
         int x=5,y=6;
         int tmp9 = x; x=y; y=tmp9;
         int tmp10 = x; x=y; y=tmp10;
      }
      

      【讨论】:

        【解决方案8】:

        他们可以。他们通常不应该这样做。

        为什么这个函数需要是宏?你可以内联它吗?

        【讨论】:

        • 除非随着程序的增长和支持问题的出现,您会发现,维护由宏生成的代码同样不容易出错。真是一团糟。
        【解决方案9】:

        如果您使用 c++ 使用内联,或使用 -o3 和 gcc,它将为您内联所有函数。 我还是不明白为什么要宏化这个函数。

        【讨论】:

          猜你喜欢
          • 2020-10-26
          • 1970-01-01
          • 2011-08-07
          • 2023-04-11
          • 2016-06-21
          • 1970-01-01
          • 2011-07-14
          • 1970-01-01
          • 2018-03-09
          相关资源
          最近更新 更多