【问题标题】:Why this macro is defined as ({ 1; })?为什么这个宏被定义为 ({ 1; })?
【发布时间】:2012-12-05 23:30:12
【问题描述】:

在 Linux 的多个 ARM 后端,我在文件 clkdev.h 中看到了这个宏定义:

#define __clk_get(clk) ({ 1; })

参见例如./arch/arm/mach-versatile/include/mach/clkdev.h

此宏正在使用 GCC 扩展 Statements and Declarations in Expressions

后来这个宏被用在文件./drivers/clk/clkdev.c,函数clk_get_sys()

 if (cl && !__clk_get(cl->clk))
         cl = NULL;

我想知道为什么不在这里使用一个简单的宏:

#define __clk_get(clk) (1)

编辑:

我使用以下 grep 模式在整个内核源代码中发现了此构造的一些其他用法:

grep -R '({[[:space:]]*[a-zA-Z0-9_()+=/!&*>., ?:-]\+[[:space:]]*;[[:space:]]*})' .

以下是部分比赛:

./kernel/trace/trace_selftest.c:# define trace_selftest_startup_dynamic_tracing(trace, tr, func) ({ 0; })
./kernel/profile.c:#define create_hash_tables()         ({ 0; })
./include/asm-generic/bug.h: * Use of ({0;}) because WARN_ON_SMP(x) may be used either as
./include/asm-generic/bug.h:# define WARN_ON_SMP(x)         ({0;})
./include/linux/key.h:#define key_get(k)            ({ NULL; })
./include/linux/key.h:#define key_get(k)            ({ NULL; })
./include/linux/audit.h:#define audit_alloc(t) ({ 0; })
./include/linux/audit.h:#define audit_bprm(p) ({ 0; })
./include/linux/audit.h:#define audit_sockaddr(len, addr) ({ 0; })
./include/linux/audit.h:#define audit_log_bprm_fcaps(b, ncr, ocr) ({ 0; })
./include/linux/audit.h:#define audit_log_start(c,g,t) ({ NULL; })
./include/linux/atalk.h:#define atalk_proc_init()   ({ 0; })
./include/linux/ftrace.h:#define register_ftrace_function(ops) ({ 0; })
./include/linux/ftrace.h:#define unregister_ftrace_function(ops) ({ 0; })
./include/linux/ftrace.h:#define ftrace_regex_open(ops, flag, inod, file) ({ -ENODEV; })
./include/linux/ftrace.h:#define ftrace_set_filter(ops, buf, len, reset) ({ -ENODEV; })
./include/linux/ftrace.h:#define ftrace_set_notrace(ops, buf, len, reset) ({ -ENODEV; })
./include/linux/cpu.h:#define unregister_hotcpu_notifier(nb)    ({ (void)(nb); })
./include/linux/proc_fs.h:#define proc_net_fops_create(net, name, mode, fops)  ({ (void)(mode), NULL; })
./arch/powerpc/include/asm/pgtable-ppc64.h:#define pgd_set(pgdp, pudp)  ({pgd_val(*(pgdp)) = (unsigned long)(pudp);})
./arch/sh/math-emu/math.c:#define WRITE(d,a)    ({if(put_user(d, (typeof (d)*)a)) return -EFAULT;})
./arch/sh/math-emu/math.c:#define READ(d,a) ({if(get_user(d, (typeof (d)*)a)) return -EFAULT;})
[...]

注意:构造 ({if(put_user(d, (typeof (d)*)a)) return -EFAULT;}) 似乎是复合语句的一个很好的用法。不过这个也可以换成更典型的do { if(put_user(d, (typeof (d)*)a)) return -EFAULT; } while(0)

grep 返回的一个匹配很有趣:在./include/asm-generic/bug.h 中有一个({ 0; }) 用法的评论。这与AndreyTanswer 完全相同。

确实,不能使用((void)0),因为它不能用作右值。 ({ 0; }) 在每种情况下都有效。

因此,如果您有一个可以像函数一样使用的宏,它返回一个可以使用或不可以使用的值,复合语句似乎是您唯一的选择。

__clkget() 从未用作其他任何东西作为 r 值

一些链接

【问题讨论】:

  • 我的猜测是宏曾经有一些肉,并且在某些时候有人破解它总是评估为 1 而没有删除 GCC 扩展括号语法。
  • 不,现在我认为 GCC 扩展括号语法可能会破坏“条件表达式始终为真”的警告?
  • @phonetagger 这是 gcc 警告吗?但我确实看到可能会出现 MSVC C4127 警告。
  • @JosephQuinsey - 我自己没有收到这样的警告,但是和我一起工作的人一直在调整其他人签到的代码,因为他自己的 linux 机器上的 GCC 版本更新了很多比其他人在服务器上使用的版本,其他人的正常编码实践显然在他使用的任何版本上都不能很好地工作(就警告而言)。我不知道他用的是什么版本。
  • @H2CO3 我认为没有人可以使用任何 Microsoft 产品编译 vanilla Linux 内核。

标签: c linux gcc macros linux-kernel


【解决方案1】:

为什么这个宏被定义为 ({ 1; })?

这完全取决于程序员的编码风格。它所做的只是返回值1。例如,在“include/asm-generic/clkdev.h”中的x86arch 上,__clk_get 定义为

static inline int __clk_get(struct clk *clk) { return 1; }

也在 linux/arch/c6x/include/asm/clkdev.h 中

static inline int __clk_get(struct clk *clk)
{
        return 1;
}

【讨论】:

  • 在为({ 1; }) 查找内核源代码时,除了__clk_get() 宏之外没有其他结果。
  • 也许您只使用 ARM 的内核源代码树。尝试使用完整的内核代码库。 lxr.linux.no/linux+v3.7.1/include/asm-generic/clkdev.h#L20
  • 这是一个风格问题,在某种程度上,对编译器扩展的错误构思、无偿使用是风格。
【解决方案2】:

我注意到在-Wall 模式下,独立的(1); 表达式语句会生成“无效的语句”警告,而独立的({ 1; }); 表达式语句不会产生警告。

也许在代码中的某个地方,它们以某种方式最终以独立的__clk_get 调用而忽略了结果。 (1) 定义会导致此类调用发出警告,而 ({ 1; }) 会保持安静,同时在其他情况下产生相同的效果。

【讨论】:

  • 我怀疑这有点接近事实。有时,某些宏以特别奇怪的方式定义,因为希望 a) 在各种不同的设置中正常工作(甚至可能防止某些错误用法)而没有任何意外的副作用 b) 无论如何都避免生成编译器诊断宏最终被使用...以“明显”的方式定义它可能是那里的 95%,但某些不寻常的情况可能需要一些看起来很奇怪的调整...
【解决方案3】:

一种可能的解释是防止在不良情况下使用。
这对于提高代码可移植性很有用 - 如果另一个架构的宏实现失败,我们希望这个也失败。

示例:static int x = __clk_get(clk); - 使用时钟进行静态初始化毫无意义。
使用#define __clk_get(clk) (1),它将起作用。
使用#define __clk_get(clk) ({ 1; }),它将失败。

【讨论】:

  • 一个非常量表达式可以在标准C中得到,比如1 ? 1 : 1
  • @Kaz,也许__clk_get 也可以这样实现。
  • @Kaz: ?: 在标准 C 的常量表达式中是允许的。实际上,几乎所有的运算符都是允许的。只要所有操作数都是常量表达式,运算符就会产生常量表达式。所以,1 ? 1 : 1 是 C 中的常量表达式。是什么让您认为它应该是非常量的?
  • @AndreyT 我的错;这是我们想要的逗号运算符,而不是三元:常量表达式不应包含赋值、递增、递减、函数调用或逗号运算符,除非它们包含在未计算的子表达式中。 [ ISO 9899:1990 6.6 常量表达式,约束] ...虽然我似乎记得三元表达式在 C90 中也不是常量。
  • @AndreyT 问题是,如果编译器没有按要求实际诊断它,它就没有用。
猜你喜欢
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多