高级C问题:请解释C构造*({foo(&bar);&bar;})

cle*_*ght 5 c gcc struct initializer linux-kernel

最终,这是在Linux内核源代码的complete.h中学习代码时出现的一个C问题,在那里我看到了以前从未在C中使用过的C技术。尽管对它的操作有一个模糊的感觉,但我想通过精确的描述来微调我的理解,而且我不确定如何在不费劲的情况下用Google搜索答案。

来自Linux内核的complete.h的相关代码行:

struct completion {
    unsigned int done;
    wait_queue_head_t wait;
};

#define COMPLETION_INITIALIZER_ONSTACK(work) \
    (*({ init_completion(&work); &work; }))

#define DECLARE_COMPLETION_ONSTACK(work) \
    struct completion work = COMPLETION_INITIALIZER_ONSTACK(work)

static inline void init_completion(struct completion *x)
{
    x->done = 0;
    init_waitqueue_head(&x->wait);
}
Run Code Online (Sandbox Code Playgroud)

并在使用中:

int myFunc()
{
   DECLARE_COMPLETION_ON_STACK(comp);
   .
   .
   .
   wait_for_completion(&comp);
}
Run Code Online (Sandbox Code Playgroud)

具体来说,我想了解的代码COMPLETION_INITIALIZER_ON_STACK

我相信两个语句的括号主体{ init_completion(&work); &work; }仅产生一个值&work(NOP语句),根据我对C中的括号块的了解,该值将得出最后一个赋值的值,在这种情况下为结构的地址。

但是,所有这些内容的封闭*( )变得很有趣(这让我感到困惑)。

  1. “获取” 到底在做什么
  2. 是否导致函数init_completion()被调用(可能)?
  3. 指向作为获取的对象的结构的指针的结果是什么?
  4. 它可以在什么情况下应用?

我不确定发生了什么,如何构思它以及如何将结果分配给struct completion workin中完成的不确定DECLARE_COMPLETION_ON_STACK

任何对此的教育将不胜感激。

dbu*_*ush 6

({ ... })块中语句的语法是GCC扩展的语句表达式。它允许您运行一系列语句,其中块中的最后一条语句是一个表达式,该表达式成为完整语句表达式的值。因此,在这种情况下,语句表达式具有值&work

由于语句表达式的计算结果为&work,因此*语句表达式之前的内容即为您*&work,或者等效work为宏的值COMPLETION_INITIALIZER_ONSTACK

现在让我们看一下DECLARE_COMPLETION_ONSTACK。使用时:

DECLARE_COMPLETION_ON_STACK(comp);
Run Code Online (Sandbox Code Playgroud)

它扩展为:

struct completion comp= COMPLETION_INITIALIZER_ONSTACK(comp);
Run Code Online (Sandbox Code Playgroud)

进一步扩展为:

struct completion comp = (*({ init_completion(&comp ); ∁ }))
Run Code Online (Sandbox Code Playgroud)

分解来看,该变量comp正在使用语句表达式进行初始化。该表达式中的第一条语句是对函数的调用,该函数init_completion传递给新变量的地址。此函数设置变量的值,此变量尚未实际初始化。语句表达式中的下一个(也是最后一个)语句是语句表达式&comp的值。然后,该地址被取消引用comp,然后分配给我们comp。因此,该变量将使用自身进行有效初始化!

通常,用自身初始化变量会调用未定义的行为,因为您将尝试读取未初始化的变量,但在这种情况下不会这样做,因为变量的地址会传递给一个函数,该函数会在初始化之前为其字段分配值。

您可能会问为什么COMPLETION_INITIALIZER_ONSTACK没有这样定义:

#define COMPLETION_INITIALIZER_ONSTACK(work) \
    ({ init_completion(&work); work; })
Run Code Online (Sandbox Code Playgroud)

如果这样做,将在堆栈上创建一个临时变量。使用地址会阻止这种情况的发生。实际上,代码最初是这样做的,但是更改为您在以下提交中看到的内容:

https://github.com/torvalds/linux/commit/ec81048cc340bb03334e6ca62661ecc0a684897a#diff-f4f6d7a50d07f6f07835787ec35565bb

  • @AShelly找到了。看到我的编辑。 (2认同)