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

Koo*_* Ng 6 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); }))
Run Code Online (Sandbox Code Playgroud)

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

AnT*_*AnT 15

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

&操作者施加到的结果__get_cpu_var(var)的子表达式.这意味着__get_cpu_var返回左值.如果这确实是C,那么__get_cpu_var也必须是一个宏,因为在C语言中函数不能返回lvalues.

&操作者产生一个指针,它然后通过解除引用(整个语句表达的结果)*操作者存在于上述宏定义的最开始.所以,上面的宏基本上等同于*&__get_cpu_var(var)表达式.

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

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

int a, b;
Run Code Online (Sandbox Code Playgroud)

并且你想要写一个表达式,它将返回ab作为左值(假设我们要分配42给它),具体取决于选择器变量select.天真的尝试可能如下所示

(select ? a : b) = 42;
Run Code Online (Sandbox Code Playgroud)

这是行不通的,因为在C语言中,?:运算符会失去其操作数的左值.结果是rvalue,无法分配.在这种情况下,*&诀窍来到救援

*(select ? &a : &b) = 42;
Run Code Online (Sandbox Code Playgroud)

现在它按预期工作.

这正是原始海报的宏定义包含一个看似冗余的应用程序的原因*和原因&.因此,你可以get_cpu_var在assgnment的任何一侧使用上面的宏

something = get_cpu_var(something);
get_cpu_var(something) = something;
Run Code Online (Sandbox Code Playgroud)

如果没有这个技巧,你只能get_cpu_var在右侧使用.

在C++语言中,使用引用可以实现相同的效果.在C中我们没有引用,所以我们使用这样的技巧.