有人可以为我解释一下这个宏#define g_once_init_enter(location)吗?

Could someone explain this macro #define g_once_init_enter(location) which is in glib for me?

当我读到这个宏时,我有点困惑: #define g_once_init_enter(location) 在 glib 库中定义。

#define g_once_init_enter(location)                            \
  (G_GNUC_EXTENSION({                                          \
  G_STATIC_ASSERT(sizeof *(location) == sizeof(gpointer));     \
  (void) (0 ? (gpointer) *(location) : 0);                     \
  (!g_atomic_pointer_get (location) &&                         \
  g_once_init_enter (location));                               \
}))

这行的作用是什么:(void) (0 ? (gpointer) *(location) : 0); 最后一行又是g_once_init_enter(location),这是一个死循环吗? 感谢您的回复。

我觉得这像是隐式类型检查。

宏显然希望传递一个 gpointer 的地址(无论那是什么),但是宏没有参数类型,所以它不能这么说。

相反,它断言 sizeof *(location)sizeof(gpointer) 相同,这验证了 location 可以取消引用并且它指向的内容具有正确的大小(否则 G_STATIC_ASSERT 行无法编译)。

然后它确保 (location) 指向的任何内容都可以通过编译转换 (gpointer) *(location) 转换为 gpointer。这一行没有其他效果(并且在运行时永远不会进行转换);如果转换不知何故无效,它只会让编译器抱怨。

宏不会递归展开。最后一行 g_once_init_enter(location) 还剩下 as-is,所以一定有一个实际的 g_once_init_enter 函数可以在这里调用。

这是一些非常难看的代码。一行一行:

  • G_GNUC_EXTENSION 显然与 __extension__ 相同,后者是将错误代码隐藏在地毯下的肮脏把戏。它告诉 gcc 编译器将 non-standard 代码视为正确的 C。
  • G_STATIC_ASSERT(sizeof *(location) == sizeof(gpointer)); 是 C11 之前的静态断言,如果 pointed-at 数据与类型 gpointer 的大小不同,它将扩展为某种神秘的编译器错误。这是获得一点点类型安全的尝试。
  • (void) (0 ? (gpointer) *(location) : 0); 是通过编写从未执行但包含强制转换的行来实现类型安全的一些拙劣尝试。值得注意的是,C 中的几乎任何类型都可以转换为几乎任何其他类型而不会引发编译器错误,因此这一行根本没有实现太多。
  • 其余的是两个函数调用,其中 g_once_init_enter 仅在 g_atomic_pointer_get returns 0/NULL 的情况下执行。

总的来说,宏试图在 C 中实现类型安全,但不太成功。主要是因为它是一种不可能完成的任务,至少在 C11 之前是这样。

值得注意的是,有一个名为 gpointer 的类型是有问题的,因为这意味着该类型根本不是指针,或者它是隐藏在 typedef 后面的指针。无论哪种情况,都是非常糟糕的做法。

在现代 C 中,你可能会用这个来代替整个混乱:

#define g_once_init_enter(location) \
  _Generic(*(location), gpointer :  \
  !g_atomic_pointer_get (location) && g_once_init_enter (location) )