在Linux内核源代码中遇到Abstruse #define宏

时间:2012-04-15 21:30:05

标签: c gcc linux-kernel c-preprocessor

get_cpu_var marcro,定义如下

 29 #define get_cpu_var(var) (*({                           \
 30         extern int simple_identifier_##var(void);       \
 31         preempt_disable();                              \
 32         &__get_cpu_var(var); }))

似乎是不可理解的。我假设它是一种函数宏,它返回一个变量指针(基于星号)或者它是某种函数指针。我甚至接近它?有人能启发我吗?

1 个答案:

答案 0 :(得分:15)

您在开场({和结束})之间看到的是语句表达式 - GCC编译器的非标准功能,它允许将复合语句嵌入到C表达式。这种语句表达式的结果是({})中最后一个表达式语句。在您的情况下,&__get_cpu_var(var)

&运算符应用于__get_cpu_var(var)子表达式的结果。这意味着__get_cpu_var返回左值。如果这确实是C,那么__get_cpu_var也必须是一个宏,因为在C语言中函数不能返回左值。

&运算符生成一个指针(整个语句表达式的结果),然后由上面宏定义最开头的*运算符取消引用。因此,上面的宏基本上等同于*&__get_cpu_var(var)表达式。

有些人可能会问为什么它被实现为*&__get_cpu_var(var)而不只是__get_cpu_var(var)。这样做是为了保留__get_cpu_var(var)结果的左值。语句表达式的结果总是rvalue,即使({})中的最后一个stetement是左值。为了保持结果的左值,使用了众所周知的*&技巧。

这个技巧不仅限于GCC语句表达式。它通常用于普通的日常C编程。例如,假设您有两个变量

int a, b;

并且您希望编写一个表达式,该表达式将ab作为左值返回(假设我们要为其分配42),具体取决于选择器变量{{ 1}}。天真的尝试可能如下所示

select

这不起作用,因为在C语言中(select ? a : b) = 42; 运算符失去了其操作数的左值。结果是rvalue,无法分配。在这种情况下,?:技巧来拯救

*&

现在它按预期工作。

这正是原始海报宏定义包含*(select ? &a : &b) = 42; *的看似冗余应用的原因和原因。因此,您可以在assgnment的任何一侧使用上述&

get_cpu_var
如果没有这个技巧,你只能在右侧使用something = get_cpu_var(something); get_cpu_var(something) = something;

在C ++语言中,使用 references 可以达到同样的效果。在C中我们没有引用,所以我们使用这样的技巧。