【问题标题】:extern "C" - before or after library header includes?extern "C" - 在库头包含之前还是之后?
【发布时间】:2021-12-07 04:53:16
【问题描述】:

我正在编写一个 C 库,它可能对编写 C++ 的人有用。它有一个如下所示的标题:

#ifndef FOO_H_
#define FOO_H_

#include <bar.h>

#include <stdarg.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

void foo_func();

#ifdef __cplusplus
}
#endif
#endif 

我想知道 - 我应该在包含标头包含指令之前移动 extern "C" 位吗?尤其是看到在实践中,其中一些标头本身可能有一个extern "C"?

【问题讨论】:

  • 标准库头文件在必要时已经包含在其中。为你自己的声明担心。
  • @user207421:我稍微扩大了问题的范围,以便答案可能同时涉及标准库和其他标题。另外,什么保证标准库头文件在必要时会有 extern C?这是语言标准的要求吗?
  • 一些标头是在假设最外层“范围”具有 C++ 语言链接的情况下显式编写的。他们可能会使用__cplusplus 来删除模板声明,如果您按照自己的意愿将它们包装起来,您的标题将从根本上被破坏。因此,如前所述,使 your 声明正确,并让其他标头不受阻碍地做他们的事情。即使是标准库头文件也可能会损坏(因为实施者可能会认为无论如何都要共享它,然后在里面做一些专家友好的事情)。

标签: c++ c include cross-language extern-c


【解决方案1】:

不,一般情况下,您不应将其移动以包含标题。

extern "C" 用于指示函数正在使用 C 调用约定。该声明对变量和#defines 没有影响,因此无需包含这些。如果#includeextern "C" 块内,这会有效地修改该头文件中的函数声明!

背景:如果没有extern "C" 声明,使用C 编译器编译时,假定函数遵循C 约定,使用C++ 编译器编译时,假定遵循C++ 约定。如果 C 和 C++ 代码使用相同的头文件,则会出现链接器错误,因为编译后的函数在 C 和 C++ 中具有不同的名称。

虽然可以将所有代码放在#ifdef 块之间,但我个人不喜欢它,因为它实际上只用于函数原型,而且我经常看到人们将它复制粘贴到不应该的地方'不是。最干净的方法是将其保留在应有的位置,即 C/C++ 头文件中的函数原型周围。

所以,要回答您的问题“我应该在包含标头包含指令之前移动 extern "C" 位吗?”,我的回答是:不,您不应该。

但这可能吗?是的,在许多情况下,这不会破坏任何东西。有时甚至有必要,如果外部头文件中的函数原型不正确(例如,当它们是 C 函数并且您想从 C++ 调用它们时)并且您无法更改该库。

但是,在某些情况下这样做会破坏构建。这是一个简单的示例,如果您使用 extern "C" 包装包含,则无法编译:

foo.h:

#pragma once

// UNCOMMENTING THIS BREAKS THE BUILD!
//#ifdef __cplusplus
//extern "C" {
//#endif

#include "bar.h"

bar_status_t foo(void);

//#ifdef __cplusplus
//}
//#endif

foo.c:

#include <stdio.h>
#include "foo.h"
#include "bar.h"

bar_status_t foo(void)
{
    printf("In foo. Calling bar wrapper.\n");
    return bar_wrapper();
}

bar.h:

#pragma once

typedef enum {
    BAR_OK,
    BAR_GENERIC_ERROR,
    BAR_OUT_OF_BEAR,
    // ...
} bar_status_t;

extern "C" bar_status_t bar_wrapper(void);
bar_status_t bar(void);

bar.cpp:

#include <iostream>
#include "bar.h"

extern "C" bar_status_t bar_wrapper(void)
{
    std::cout << "In C/C++ wrapper." << std::endl;
    return bar();
}

bar_status_t bar(void)
{
    std::cout << "In bar. One bear please." << std::endl;
    return BAR_OK;
}

ma​​in.cpp:

#include <stdio.h>
#include <stdlib.h>
#include "foo.h"
#include "bar.h"

int main(void)
{
    bar_status_t status1 = foo();
    bar_status_t status2 = bar();
    return (status1 != BAR_OK) || ((status2 != BAR_OK));    
}

取消注释 a.h 中的块时,我收到以下错误:

main2.cpp:(.text+0x18): undefined reference to `bar'
collect2.exe: error: ld returned 1 exit status
Makefile:7: recipe for target 'app2' failed

没有,它构建得很好。仅从 foo 和 bar 调用 C 函数的 C 主程序无论哪种方式都可以正常构建,因为它不受 #ifdef __cplusplus 块的影响。

【讨论】:

  • “仅当您包含一个用 C 编写的外部库并且不包含提到的 C++ 指令时”
  • @einpoklum "好吧,如果我包含一个外部库,显然它是用 C 编写的(否则我不会包含)" 为什么?您还可以包含一个 C++ 库。我不知道你目前是在用 C 还是 C++ 编程,因为问题都被标记了,但这并不重要,只有在混合两种语言时才需要 extern "C"
  • @wovano: 为什么会这样? 因为foo 是用 C 编写的。它的源代码只包含其他 C 语言头文件。但我会在问题文本中澄清这一点。
  • @einpoklum,如果foo 是用 C 语言编写的,那么您当前的标题确实是我的编写方式 :-)
【解决方案2】:

令人惊讶的是。现在阅读标准后,我什至会写

#ifndef FOO_H_
#define FOO_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <bar.h>

#include <stdarg.h>
#include <stddef.h>


void foo_func();

#ifdef __cplusplus
}
#endif
#endif 

有两个原因:

1 嵌套extern "C" 没问题,所以你的bar.h 包含就可以了。这样看起来更清晰更重要

2 如果 C++ 用户不想这样做,您必须在 C 标头周围加上 extern "C",才能真正实现可移植性。

因为我只是查看了 C++ 标准和 16.5.2.3 Linkage [using.linkage] 状态

使用外部链接声明的 C 标准库中的名称是否具有 extern “C”或外部“C++”链接是实现定义的。它是 建议实现为此使用外部“C++”链接 目的.1

为了安全起见,您确实应该在这些包含周围加上extern "C",但是您不必这样做,因为这暗示了 D.9 C 标头 [depr.c.headers] 中的标头,例如 @987654326 @你正在使用的。

【讨论】:

  • 嗯,是的,但我不明白。自然,在 C++ 中,我们鼓励您使用 &lt;whater&gt; 而不是 &lt;whatever.h&gt;。但这一事实并不符合“是”AFAICT 的条件。
  • 我认为“实现”是指编译器和相应的库和头文件。它不是指您和我(假设您没有编写编译器;p)。我认为这意味着允许 C++ 编译器在 C++ 中实现标准 C 库,具有 C++ 链接,但当然相应的头文件应该与代码对应。所以它不会对使用这些库(和头文件)的应用程序产生任何影响。
  • 令人惊讶的是。 ...不过,IMO 将是一个严重损坏的实现。我可能不想将标准 C 头文件放在 C++“守卫”中,以免它爆炸,我可以说,“我们不支持你的实现。”
  • 我也对此感到惊讶。我给出了一个答案,我证明总是用extern "C" 包装标题会破坏构建。另一个答案提供了一个可靠来源的链接,在该来源中问题得到了明确的回答。然而,一个提到“它看起来更重要”作为一个原因的答案得到了更多的支持......
  • @wovano 嘿,你的回答比我的得票多(至少总和):-)。有时甚至来自可靠资源的好的常见问题解答都不知道一些标准的惊喜,特别是对于很少相关/使用的东西。但我承认可靠的常见问题解答表明我的解释是错误的。然而,这里的一些人只是声称自己没有研究标准或实施文档/官方讨论。不要和你吵架:-)
【解决方案3】:

C++ FAQ 对此进行了介绍。

首先,How to include a standard C header file in C++ code? 不需要什么特别的东西,因为标准 C 头文件可以与 C++ 无缝协作。所以您不需要stdarg.hstddef.h 包装在extern "C" 中。

那么,对于非标准的 C 头文件,有两种可能:要么您不能更改头文件,要么您可以更改头文件。

当您can't change the C header 时,将#include 包裹在extern "C" 中。

// This is C++ code
extern "C" {
  // Get declaration for f(int i, char c, float x)
  #include "my-C-code.h"
}
int main()
{
  f(7, 'x', 3.14);   // Note: nothing unusual in the call
  // ...
}

当您 can change the header 时,编辑它以有条件地将 extern "C" 包含在标头本身中:

#ifdef __cplusplus
extern "C" {
#endif

. . .

#ifdef __cplusplus
}
#endif

就您而言,这取决于您是否可以控制bar.h 的内容。如果它是您的标题,那么您应该修改它以包含 extern "C" 并且 not 包装 #include 本身。

无论以何种方式/何时/何地包含它们,标题都应以相同的方式工作。将#include 包裹在extern "C"#pragma pack、特殊#defines 等中,应保留用于最后的解决方法,因为这可能会干扰标头在不同场景中的行为,从而降低系统长期的可维护性运行。

正如 StoryTeller 在comments 中所说:

某些标头是在假设最外层“范围”具有 C++ 语言链接的情况下显式编写的。他们可能会使用__cplusplus 来删除模板声明,如果您按照自己的意愿将它们包装起来,您的标题将从根本上被破坏。因此,如前所述,使 your 声明正确,并让其他标头不受阻碍地做他们的事情。甚至标准库头文件也可能会损坏(因为实施者可能会认为无论如何都要共享它,然后在里面做一些专家友好的事情)。

请注意,标准 C 头文件可以使用 C 链接实现,但也可以使用 C++ 链接实现。在这种情况下,将它们包装在 extern "C" 中可能会导致链接错误。

【讨论】:

  • 该常见问题解答针对的是编写 C++ 的库 user;我是图书馆作者,正在写C。
  • 我会说 C++ 标准中的第 16.5.2.3 节链接 [using.linkage] 胜过常见问题解答,即使在 isocpp 网站上也是如此。
  • @einpoklum,常见问题解答也针对图书馆作者。您的问题基本上在这个问题中得到了回答:How can I modify my own C header files so it’s easier to #include them in C++ code?(上面的问题描述了如果未以这种方式编写包含 to-bo 的标头的解决方法,您可以做什么。)
  • @Superlokkus [using.linkage]/2 说“使用外部链接声明的 C 标准库中的名称是否具有外部“C”或外部“C++”链接是实现定义的。 ",这意味着我们不知道标准库使用什么链接,因此应该避免将其包装在任何 extern-anything 中。标准所说的和常见问题解答之间没有差异。只是不要这样做。
猜你喜欢
  • 2013-05-16
  • 1970-01-01
  • 1970-01-01
  • 2011-07-27
  • 1970-01-01
  • 1970-01-01
  • 2014-05-27
  • 2012-06-25
相关资源
最近更新 更多