使用extern "C"时,有带括号和不带括号的区别吗?

15

所以,在James Kanze和Loki Astari的指导下,我在考虑这个问题:关于C语言的链接问题

extern "C" int foo1 (void (*)());
extern "C" { int foo2 (void (*)()); }

在我的学习过程中,我认为foo1只接受具有C++链接的函数指针,而foo2只接受具有C链接的函数指针。我的理解正确吗?C++标准中是否有特定的参考资料来解释上面示例中的差异? 编辑:为了让每个人都能跟上,这里是C ++ 11草案标准中相关部分的pastebin
3个回答

10

foo1需要一个指向C函数的指针,如[dcl.link] 7.5p4所示。

在链接规范中,指定的语言链接适用于所有函数声明符、具有外部链接的函数名和在链接规范中声明的具有外部链接的变量名的函数类型。 [示例:

extern "C" void f1(void(*pf)(int));
                                                                 // 函数f1及其函数类型具有C语言链接;pf是指向C函数的指针

这个示例直接适用于foo1,添加了重点突出我认为的原因。函数的参数列表包括一个参数的函数声明符,所有函数声明符都受到链接规范的影响。这适用于大括号和非大括号链接规范。

如果不使用大括号,则存在一些差异,名称会自动成为extern,并且禁止显式使用存储说明符。

extern "C" int i; // not a definition

int main() {
    i = 1; // error, no definition
}

extern "C" static void g(); // error

作为需要考虑这种差异的一个例子,考虑包含以下内容的标头:
extern "C" int a;
extern "C" double b;
extern "C" char c;

有些人可能会想要将此更改为:

extern "C" {
    int a;
    double b;
    char c;
}

但这是不正确的,因为这会将声明转换为定义。相反,使用 extern "C" {} 的正确代码如下:

extern "C" {
    extern int a;
    extern double b;
    extern char c;
}

完全可以让具有 C 语言链接的函数接受一个具有 C++ 语言链接的函数指针:extern "C++" typedef void (*CPPFUNC)(); extern "C" void foo(CPPFUNC); - bames53
啊,谢谢。不过要实际调用它可能有点棘手(除非它是用C++实现的;-)。答案已被接受。 - jxh
我认为这是错误的!extern "C" 是用于声明函数在链接名称(名称混淆)中不应包含参数类型,这是 C++ 进行函数重载的方式。这样,C++ 就可以使用纯 C 库,例如 stdlib。 - epatel
1
@epatel,C和C++ ABI没有必须相同的要求,标准非常明确地规定了函数类型具有与函数名称分离的语言链接。它特别指出的一个例子是调用约定。 - bames53
@epatel:考虑从第一段到7.5节的内容:<quote>即使它们在其他方面相同,具有不同语言链接的两种函数类型也是不同的类型。</quote>这意味着链接是类型的一部分。因此,指向C函数的指针与指向C++函数的指针是不同的。由于它绑定到类型,所以它不仅仅是名称混淆。 - jxh
显示剩余5条评论

2

当你有许多声明和定义时,可以使用大括号。通常,您可以在头文件中看到开始和结束的C代码,以便在C++中使用。

#ifdef __cplusplus
extern "C" {
#endif

// C stuff here to be available for C++ code

#ifdef __cplusplus
}
#endif

我可以推荐阅读关于“名称混淆”的文章http://en.wikipedia.org/wiki/Name_manglingextern "C"是回退到C链接名称约定的关键。

1
extern "C" int foo1 (void (*)());
extern "C" { int foo2 (void (*)()); }

它们是一样的。使用大括号的主要原因是当您有多个函数时,例如:

extern "C" int foo1 (void (*)());
extern "C" int foo2 (void (*)());
extern "C" int foo3 (void (*)());
extern "C" int foo4 (void (*)());

这可以更简单地写成:

extern "C" {
    int foo1 (void (*)());
    int foo2 (void (*)());
    int foo3 (void (*)());
    int foo4 (void (*)());
}

此外,如果您正在尝试创建一个可同时在C和C++中使用的头文件,您可能想将其编写为:

#ifdef __cplusplus
extern "C" {
#endif

    int foo1 (void (*)());
    int foo2 (void (*)());
    int foo3 (void (*)());
    int foo4 (void (*)());

#ifdef __cplusplus
}
#endif

顺便提一句,我不知道有哪些编译器在函数指针的“C++链接”或“C链接”之间存在差异。当我们谈论C或C++链接时,我们谈论的是名称如何被编译器修饰。对于函数指针,你正在传递一个指针,因此名称无关紧要。重要的是调用约定相同,但通常情况下C和C++的调用约定是相同的,因为人们自由地混合这些语言。


该标准讨论了名称和函数类型在"C"和"C++"之间的链接方式。它适用于C和C++ ABI不同的情况。 - bames53
我已经编辑了答案,改为说“我不知道有任何编译器存在差异”,而不是“不存在差异”。对于一个新的程序员来说,使用普通的编译器,没有任何重要的区别。 - user9876

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