【问题标题】:Parenthesis operator in C. What is the effect in the following codeC中的括号运算符。下面代码中的作用是什么
【发布时间】:2011-02-07 11:30:54
【问题描述】:

当我在禁用宏时使用以下代码时,我正在使用宏来启用/禁用跟踪:

int main()
{

  ("Hello world");

}

这段代码是有效的,我得到了想要的效果(禁用宏时没有任何反应),但我无法弄清楚到底发生了什么。编译器是否将括号视为“无名”方法声明?

为了更清楚,代码是:

 #ifdef TRACE

    #define trace printf("%s %d -> ",__FILE__, __LINE__);printf
 else

    #define trace
 #endif

int main()
{

  trace("Hello world");

}

提前致谢。

【问题讨论】:

  • 像这样的多语句宏有点棘手的一点是,它们不能像if 语句那样工作(你不能说像if (condition) TRACE("bad"); 这样的话) - 也许你可以在这里使用&& 代替分号 (?) 来解决这个问题 - 或者只是小心使用宏;)

标签: c parentheses


【解决方案1】:

如果缺少函数名称,如您的第一个示例所示,则它不是“括号运算符”。它只是改变运算符和操作数之间关联的表达式的句法元素。在这种情况下,它根本什么都不做。你所拥有的只是一个表情

"Hello world";

其计算结果为char * 类型的值,并且该值被忽略。您可以将该表达式包含在一对冗余的 ()

("Hello world");

这不会改变任何事情。

用同样的方式你可以写

(5 + 3);

在您的代码中间并获得一个计算结果为值8 的表达式,该表达式会立即被丢弃。

通常编译器不会为没有副作用的表达式语句生成代码。事实上,在 C 语言中,每个表达式语句的结果都会被丢弃,因此唯一“有意义”的表达式语句是具有副作用的表达式语句。编译器通常非常擅长检测无效语句并丢弃它们(有时会发出警告)。

警告可能很烦人,所以编写无效的表达式语句,如

"Hello world";

可能不是一个好主意。通常,编译器会将转换为 void 识别为不生成此警告的请求

(void) "Hello world";

所以你可以考虑相应地重新定义你的宏。

当然,使用上述trace 技术,您必须记住,如果您将确实具有副作用的东西作为宏的参数

trace("%d\n", i++);

然后在“禁用”形式下,它将如下所示

("%d\n", i++);

(两个子表达式,由逗号运算符链接成一个表达式)。在这种情况下,增加i 的副作用仍然存在,它不会被禁用。整个就相当于plain了

i++;

如果你使用函数调用作为参数

trace(get_trace_name());

“禁用”表单将如下所示

(get_trace_name());

编译器可能不够聪明,无法意识到应该放弃对get_trace_name() 的调用。因此,在使用宏时要小心。避免带有副作用的参数,避免带有函数调用的参数,当然,除非您打算在禁用实际跟踪时保留副作用。

【讨论】:

  • 另一件事是避免trace 的参数中的副作用很重要,但无论如何这是个好主意。
  • @Donal Fellows:谢谢。我只是在写一些关于它的额外内容:)
  • 这是一个详尽的解释,感谢您花时间编写它:)
【解决方案2】:

它是否有效可能取决于您作为参数传递给宏的确切内容(请参阅 AndreyT 提到的副作用问题)。在这种情况下,它是良性的。然而,以下可能更安全,因为在处理宏时不会插入任何文本并且未定义 TRACE:

#ifdef TRACE
    #define trace printf("%s %d -> ",__FILE__, __LINE__);printf
#else
    #define trace( ... )
#endif

假设您的编译器支持可变参数宏。如果是这样,以下可能是一个更好的定义:

#ifdef TRACE
    #define trace( fmt, ...) printf("%s %d -> " fmt, __FILE__, __LINE__, __VA_ARGS__ ) ;
#else
    #define trace( ... )
#endif

请注意,"%s %d -> "fmt 之间没有逗号是故意的并且是必需的。另请注意,fmt 参数 必须 是文字字符串常量,以便发生相邻的字符串文字连接 - 任何类型的变量都会产生错误,但 bad practice 使用无论如何,格式说明符的变量。

【讨论】:

  • 你在那里遇到了严格的 C99 问题。显然,在这种情况下调用它时,你必须在字符串后面加一个逗号。 IMO 这是 C99 规范错误的地方。
  • @Donal Fellows:你确定吗?在这种情况下,约束是 fmt 必须 是字符串文字,以便利用相邻的字符串文字连接;我应该清楚这一点。在任何情况下,最好只使用字符串文字常量作为格式说明符。例如,它允许trace( "var = %d", var ) ; 之类的代码输出main.c 10 -> var = 24。也许我误解了你关于 C99 的观点;你能引用这个断言的 ISO 参考吗?
  • 更新,来自工作草案 N1905=05-0165(因为它是免费的)2.3.4 字符串文字,第 3 段“在翻译阶段 6(2.1) 中,连接相邻的字符串文字” 。我意识到工作草案不是最终的,但 C 和 C++ 的这一长期存在的方面会改变似乎令人难以置信。
  • 问题在于宏的“调用”。问题(根据我搜索的页面)是定义的参数列表中有逗号的地方,它们必须在实际参数中匹配。这意味着trace("%d",10) 有效,但trace("foobar") 是非法的; trace("foobar",) 可以,但是 C 之道中缺少这一点。(好吧,无论如何,IMO)。
  • @Donal Fellows:啊,我明白了;好点子。考虑到宏提供的实用程序,我想我可以接受trace("%s", "foobar")
【解决方案3】:
("Hello world");

是一个表达式,返回一个指向字符串的常量指针。该值未被消耗。

括号没有具体作用,可以省略:

"Hello world";

【讨论】:

  • 但是如果我有一个更详细的 printf 会发生什么?例如,如果不是“Hello World”,我有:
    int main
    { int x; char * str = "你好世界"; trace("测试, %d, %s", x, str);跟踪将被替换为空(跟踪禁用情况),留下以下表达式: ("Test, %d, %s", x, str);它也编译并且什么也不做。
  • ,分隔的表达式序列在C中是正确的。结果表达式的值是该序列的最后一个表达式的值。
  • 所以一切都将被评估,但“str”将是返回的值?因此,这种方法的副作用是无需对代码进行评估。
  • 如果你想追踪类似的东西,它甚至可能很危险:trace("%d",i++);
  • 非常好。观点。非常感谢您和所有其他人的帮助。
【解决方案4】:
#ifdef TRACE
   #define trace printf("%s %d -> ",__FILE__, __LINE__);printf
#else
   #define trace
#endif

int main {
  trace("Hello world");
}

宏在 C 中的工作方式是编译器将(本质上)* 对标识符进行 literal 替换。

因此,在您的情况下,有两个选项取决于 #IFDEF 的值

trace("Hello world");

可以变成

  1. printf("%s %d -> ",__FILE__, __LINE__);printf("Hello world");

  1. ("Hello world");

第一个选项是由两个printf 语句组成的有效C 代码序列。第二个选项是一系列有效的 C 代码,它由不必要的大括号内的字符串 (char *) 组成。

【讨论】:

    【解决方案5】:

    如果您的编译器支持 C99(或者您使用的是之前具有此功能的 gcc),您可以使用可变参数宏:

    #ifdef TRACE
    #define trace(...) printf("%s %d -> ",__FILE__, __LINE__);printf(__VA_ARGS__)
    #else
    #define trace(...)
    #endif
    

    这避免了参数中的副作用可能带来的问题。如果你有一个严格的 C89 编译器,你必须避免副作用......

    【讨论】:

    • VC++ 2005 及更高版本也支持可变参数宏,尽管不支持 C99。如果您连接格式字符串,您也不需要第二个 printf() (请参阅我之前的帖子);这使得宏可用任何地方函数调用是有效的。
    猜你喜欢
    • 2022-01-16
    • 2022-06-13
    • 1970-01-01
    • 1970-01-01
    • 2020-02-08
    • 1970-01-01
    • 1970-01-01
    • 2011-03-08
    • 2021-03-29
    相关资源
    最近更新 更多