5

我正在编写一个 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"?

4

3 回答 3

2

不,通常您不应该将其移动以包含标题。

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

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

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

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

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

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

富.h:

#pragma once

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

#include "bar.h"

bar_status_t foo(void);

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

富.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();
}

酒吧.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;
}

主.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));    
}

取消注释ah中的块时,出现以下错误:

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

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

于 2021-10-20T11:13:11.857 回答
0

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

#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 要真正实现可移植性extern "C",如果 C++ 用户不想这样做,您必须为 C 头文件包装一个。

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

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

为了安全起见,您确实应该围绕这些包含进行包装,但是您不必这样做,因为这暗示了您正在使用extern "C"的 D.9 C 标头 [depr.c.headers] 中的标头。<stdlib.h>

于 2021-10-20T11:32:33.510 回答
0

这在C++ FAQ中有介绍。

首先,如何在 C++ 代码中包含标准的C 头文件?不需要什么特别的东西,因为标准 C 头文件可以与 C++ 无缝协作。所以你不需要换行stdarg.hstddef.hin extern "C"

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

当您无法更改 C 标头时,将 in 包装#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
  // ...
}

当您可以更改标头时,将其编辑为有条件地包含extern "C"在标头本身中:

#ifdef __cplusplus
extern "C" {
#endif

. . .

#ifdef __cplusplus
}
#endif

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

标题应该以相同的方式工作,无论它们如何/何时/何地包含在内。包装#includein extern "C", #pragma pack, special #defines 等应保留用于最后的解决方法,因为这可能会干扰标头在不同场景中的行为,从长远来看会降低系统的可维护性。

正如 StoryTeller 在评论中所说:

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

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

于 2021-10-20T12:28:42.707 回答