为什么EXTERN_C宏在C形式中省略了"extern"?

3

在一个文件中,我有这个宏:

#if defined(__cplusplus)
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif

但是在网络上搜索,似乎这种宏定义在C路径上没有extern虽然有一些例子存在)。它们通常成对出现,更类似于EXTERN_C_BEGINEXTERN_C_END来包装一组声明...并且在C构建中是无操作的。
但这意味着在头文件中,你最终得写:
EXTERN_C_BEGIN
extern int x; /* ...but I already said extern... */
void doSomething(int y);
EXTERN_C_END

这是因为 extern "C" int x; 等同于 extern "C" { extern int x; },而不是 extern "C" { int x; }。后者只提供了"C linkage",但没有C extern的历史含义。如果您尝试将单行形式下降到C extern,则无法说 EXTERN_C extern int x;,编译器将在C++构建中在两个externs-on-one-declaration处发出警告。

由于没有开始/结束形式,这通常意味着需要更多的打字。但是我更喜欢在C情况下向编译器发出“意图声明”:

EXTERN_C int x;
EXTERN_C void doSomething(int y); /* does `extern` help the C case? */

我无法立即创造一个好的情景来证明在C中使用函数声明上的extern有助于捕获错误。但直觉上,这种冗余可能具有价值,并且正如我所说,必须在事物上加上“extern”并稍后仍然放置“extern”感觉很尴尬。
优先选择(或避免)哪种方法的原因是什么?

1
通常你会有一组宏,如 EXTERN_CEXTERN_C_BEGINEXTERN_C_END,其中 EXTERN_C 是单个声明的前缀(例如 EXTERN_C void function(void);),而在一组声明之前使用 EXTERN_C_BEGIN,在一组声明之后使用 EXTERN_C_END。在 C 中,由于函数声明默认为 extern 类型,因此可以使用空替换 EXTERN_C。在 C 代码中不会使用 extern 来表示 EXTERN_C_BEGIN,因为这很容易引入错误。 - Jonathan Leffler
据我所知,变量没有C++链接-变量名称没有被修改。(我最近检查过,在过去的几个月中,但我应该重新检查。)这意味着您不需要编写 EXTERN_C extern int x; —它可能不会失败,但这并非必要。对于例如: EXTERN_C_BEGIN / #include "c-header.h" / EXTERN_C_END 这样的情况,需要使用 #define EXTERN_C_BEGIN extern "C" {#define EXTERN_C_END } 来包装头文件。 - Jonathan Leffler
我也对你的评论“但我已经说了extern…”感到困惑。extern "C"extern是不同的东西,它们碰巧共享一个关键字。extern "C" { int x; }extern "C" { extern int x; }不同。 - M.M
@M.M "extern "C"extern是不同的东西,只是碰巧共享一个关键字。 也许是这样,但在使用这个宏时只有一个意图:“我正在编写可以在C或C++下编译的定义,这个声明是外部的,并且应该具有C链接。” 如果语言的细微差别可以折叠成一个有用的宏来做正确的事情,那似乎是一个值得追求的目标。如果不能做到,了解为什么不能做到是很有启示性的。 - HostileFork says dont trust SE
@JonathanLeffler Cppreference C语言链接的特殊规则似乎意味着当你处理命名空间时,变量的名称重整会发挥作用,并且C变量和函数被视为在它们自己的专用命名空间中。 - HostileFork says dont trust SE
显示剩余4条评论
3个回答

3
通常你会在为C编写的头文件中看到这个,然后再与C++兼容。最简单的方法是将整个头文件包装在extern "C" {}中。这样就不需要修改每一个声明,而主要内容可以保持为普通的C语言。
你所说的宏简化了这个包装过程。现在只需两行代码:
EXTERN_C_BEGIN

EXTERN_C_END

与您必须采取的稍微复杂一些的措施来隐藏C编译器中的行不同:

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif

另一个问题是全局变量相对较少(与函数相比),在函数声明中不需要使用extern。因此,对于extern宏的需求较小。

@M.M 我说的是这种情况:“它们通常成对出现,如 EXTERN_C 和 EXTERN_C_END 以包装一组声明”(链接帖子中的定义中也有 {})。OP与他们自己的宏进行对比,后者没有 {} - melpomene
好的,我现在明白他的意思了。 - M.M

1
如果有多个声明需要标记,那么你的方法比起开始/结束的方法更加冗长。我最喜欢的形式是什么都不隐藏--读代码的人不必去查找EXTERN_C被定义为什么才能理解代码。按照你的建议,读者将不得不去检查它在C模式下是extern还是空白,甚至可能是其他东西。你的方式可以工作,但我认为其中很大一部分是符合其他人阅读代码的期望。

虽然我的方法在微软代码中有历史先例,但我注意到 extern "C" int x; 作为单独一行的声明和 extern "C" { int x; } 不同。单行形式具有 extern "C" { extern int x; } 的含义。因此,如果您尝试使单行形式在 C 中衰减为 extern,则不能说 EXTERN_C extern int x;... C++ 构建不允许重复使用 extern。无论如何,它与 _BEGIN 和 _END 形式混合得不好。 - HostileFork says dont trust SE

0
在头文件中的函数声明中添加“extern”没有实际益处。因此,在宏的C++版本中存在“extern”,因为需要告诉编译器对这些函数使用C调用约定。对于C编译器,可以完全省略外部声明。
有关详细信息,请参见此答案:https://dev59.com/I3RA5IYBdhLWcg3wtwSe#856736

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接