【问题标题】:when use macro in c compound statement在 c 复合语句中使用宏时
【发布时间】:2018-06-08 03:28:03
【问题描述】:

我写了一个例子来说明我的问题,这里是使用嵌套宏来调用来检查数字是正数还是奇数。我知道使用这个调用是多余的,但正如我之前所说,它只是表明如果以嵌套方式使用宏,则会出现一些问题:

#include <stdio.h>
#include <stdlib.h>

#define num_is_positive_odd(num)                           \
({                                                         \
    int __rc = 0;                                          \
    int __num = (num);                                     \
                                                           \
    printf("%s:%d: check linenum\n", __func__, __LINE__);  \
    if (num_is_positive(__num) && num_is_odd(__num))       \
        __rc = 1;                                          \
    __rc;                                                  \
 })

#define num_is_positive(num)                  \
({                                            \
    int __rc = 0;                             \
    int __num = (num);                        \
                                              \
    if (__num > 0) {                          \
        printf("%s: number %d is positive\n", \
               __func__, __num);              \
        __rc = 1;                             \
    }                                         \
    __rc;                                     \
 })

#define num_is_odd(num)                       \
({                                            \
    int __rc = 0;                             \
    int __num = (num);                        \
                                              \
    if (__num / 2) {                          \
        printf("%s: number %d is odd\n",      \
               __func__, __num);              \
        __rc = 1;                             \
    }                                         \
    __rc;                                     \
 })



int main()
{
    int num = 4;

    if (num_is_positive_odd(num++))
        printf("%s: number %d is positive odd\n", __func__, num);

    exit(0);
}

使用命令编译时:gcc -Wunused-variable chk_comps.c

显示错误:

chk_comps.c: In function ‘main’:
chk_comps.c:7:9: warning: unused variable ‘__num’ [-Wunused-variable]
     int __num = (num);                                     \
         ^
chk_comps.c:47:9: note: in expansion of macro ‘num_is_positive_odd’
     if (num_is_positive_odd(num++))
         ^

`

有人可以帮助解释为什么以及如何解决它吗?谢谢。

【问题讨论】:

  • 这不会扩展到有效的 C 代码。
  • 这是使用名为statement expressions 的 GCC 扩展——因此规则特定于 GCC(并且可能是 Clang 模拟 GCC)。我不禁觉得你最好使用静态内联函数,而不是使用 __num 名称(错误)在整个地方使用和(重新)使用的宏堆积在宏上。
  • 请注意,通常不应创建以下划线开头的函数、变量或宏名称。 C11 §7.1.3 Reserved identifiers (部分)说: — 所有以下划线开头的标识符以及大写字母或另一个下划线始终保留供任何使用。所有以下划线开头的标识符在普通名称空间和标记名称空间中始终保留用作具有文件范围的标识符。 另请参阅What does double underscore (__const) mean in C?
  • 感谢您提供详细信息。
  • 你到底为什么使用宏而不是函数?您在那里至少有 10 个运算符优先级错误。出于对混淆的热爱,将其重写为函数。提示:如果您这样做只是为了获取调用者的__func__,那么只需执行一些包装宏,例如#define num_is_odd(num) number_is_odd(num, __func__),其中有bool number_is_odd (int num, const char* caller);

标签: c macros


【解决方案1】:

这是使用名为 statement expressions 的 GCC 扩展 - 所以规则是特定于 GCC 的(并且可能是 Clang 模拟 GCC)。

如果你运行gcc -E,你可以看到main()(我添加了void)的原始输出是:

# 41 "gccm43.c"
int main(void)
{
    int num = 4;

    if (({ int __rc = 0; int __num = (num++); printf("%s:%d: check linenum\n", __func__, 45); if (({ int __rc = 0; int __num = (__num); if (__num > 0) { printf("%s: number %d is positive\n", __func__, __num); __rc = 1; } __rc; }) && ({ int __rc = 0; int __num = (__num); if (__num / 2) { printf("%s: number %d is odd\n", __func__, __num); __rc = 1; } __rc; })) __rc = 1; __rc; }))
        printf("%s: number %d is positive odd\n", __func__, num);
    return 0;
}

当手动格式化(“炼狱”主题的变体)时,可能看起来像:

# 41 "gccm43.c"
int main(void)
{
    int num = 4;

    if (({ int __rc = 0;
           int __num = (num++);
           printf("%s:%d: check linenum\n", __func__, 45);
           if (({ int __rc = 0;
                  int __num = (__num);
                  if (__num > 0)
                  {
                      printf("%s: number %d is positive\n", __func__, __num);
                      __rc = 1;
                  }
                  __rc;
                }) &&
                ({ int __rc = 0; 
                   int __num = (__num);
                   if (__num / 2)
                   {
                       printf("%s: number %d is odd\n", __func__, __num);
                       __rc = 1;
                   }
                   __rc;
                 }
               ))
           __rc = 1;
           __rc;
         }
       ))
        printf("%s: number %d is positive odd\n", __func__, num);
    return 0;
}

int __num = (__num); 行有问题;您正在使用自身初始化变量,这并不能很好地完成(初始化之前和之后的值是不确定的)。您还使用(__num / 2) 来判断__num 是否为奇数,这是一种奇数检测奇数的方法;你应该使用(__num % 2)

现在也很明显为什么编译器会警告(其中一个)__num(变量)未使用。外部声明将num++ 分配给__num,但从未使用过初始化变量,因为int __num = (__num); 的内部出现引用了它们自己而不是外部__num,所以它不是 使用过。

你最好使用静态内联函数——像这样:

#include <stdio.h>
#include <stdlib.h>

static inline int num_is_positive(int num)
{
    int rc = 0;

    if (num > 0)
    {
        printf("%s: number %d is positive\n", __func__, num);
        rc = 1;
    }
    return rc;
}

static inline int num_is_odd(int num)
{
    int rc = 0;

    if (num % 2)        // BUG fixed
    {
        printf("%s: number %d is odd\n", __func__, num);
        rc = 1;
    }
    return rc;
}

static inline int num_is_positive_odd(int num)
{
    int rc = 0;

    printf("%s:%d: check linenum\n", __func__, __LINE__);
    if (num_is_positive(num) && num_is_odd(num))
        rc = 1;
    return rc;
}

int main(void)
{
    int num = 4;

    if (num_is_positive_odd(num++))
        printf("%s: number %d is positive odd\n", __func__, num);

    return 0;
}

【讨论】:

  • 谢谢,我猜最大的问题是功能安排不好。在我们的代码中,如果在头文件中将上述逻辑定义为静态函数,则可能在其他一些c文件中没有使用,那么会导致另一个编译问题。
  • 如果您使用static inline(和C99 或C11),那么函数是否在标头中定义并且不在任何地方使用都没有关系。未使用的函数将不会被实例化。如果您将它们设为“普通static”,那么您将拥有未使用的函数,编译器可能会抱怨它,这是正确的。这就是我使用static inline的原因。
  • (当然,在任何标头中都使用标头保护:#ifndef FOO_H #define FOO_H ... #endif。)
【解决方案2】:

你做如下宏扩展:

if (num_is_positive(__num) && num_is_odd(__num)) 

这两个都扩展为包含声明的块表达式:

int __num = (num);  

其中num 是内部宏的宏参数。由于实际的宏参数是__num,因此生成的替换文本是

int __num = (__num);  

C 声明的范围在声明完成后立即开始,并且在定义之前。因此,该声明中的 __num 都引用了相同的局部变量(因此初始化为未初始化的值)。同时,外部块中的__num 完全被内部块中的__nums 遮蔽,并且正如编译器所指出的那样,未被使用。如果您指定 -Wall 而不是只启用一个警告,编译器可能也会警告您未初始化的初始化,这可能是另一条线索。

顺便说一句,以两个下划线开头的名称是保留的;你不能创建它们。所以你的宏表现出未定义的行为。这不是您的直接问题,但既然您无论如何都必须更改名称才能强加hygiene,那么您不妨做对。

通过使用static inline 函数而不是宏来解决此问题。严重地。函数具有可预测的范围,与宏不同,宏的扩展如您所见是不卫生的。它们需要您的思考更少,更易于调试、阅读和理解,而且周期不会变慢。

【讨论】:

    【解决方案3】:

    同时完成Jonathan Leffer's answerrici's one:如果您的实际代码需要statement expressions 并且不能由static inline 完成函数(这不太可能,并且在您的函数中不是这种情况已在您的问题中显示),您应该考虑在宏中生成唯一标识符(以使它们更多hygienic)。一种可能的方法(特定于 GCC!)是使用 cpp concatenation__COUNTER__,如下所示:

    #define num_is_positive_count(num,Count)     \
    ({                                           \
        int rc_##Count = 0;                      \
        int num_##Count = (num);                 \
                                                 \
        if (num_##Count > 0) {                   \
            printf("%s:number %d is positive\n", \
                   __func__, num_##Count);       \
            rc_##Count = 1;                      \
        }                                        \
        rc_##Count;                              \
     })
    

    (顺便说一句,我避免使用以_ 开头的标识符)

    那么你需要一个双重间接

     #define num_is_positive_count2(num,Count) \
        num_is_positive_count(num,Count)
     #define num_is_positive(num) num_is_positive_count2(num,__COUNTER__)
    

    但您最好尽可能使用static inline 函数。通常是这样的!

    顺便说一句,__COUNTER__语句表达式 都是 GNU 扩展(被 GCCClang 接受),在 C11 标准 n1570 之外。在某些情况下(不在您的情况下),您可能会使用(如果您不想依赖 GNU-ism __COUNTER__...)标准 __LINE__ 而不是 GNU __COUNTER__ 并采用 约定每行调用一个宏(另请参阅this)。

    【讨论】:

    • __LINE__ 用于卫生并不是很有效,因为宏扩展中的所有宏扩展都具有相同的源代码行号。例如,它不会在 OP 的代码中工作。
    • 但是如果每个宏调用都在自己的行上,它可能会起作用
    • 如果宏扩展包含另一个使用相同 hack 的宏扩展,则不会。在 OP 的代码中,外层宏扩展了两个内层宏,不管你怎么写,它们都有相同的行号。
    猜你喜欢
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多