我正在编写一个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
我想知道-在包括头include指令之前,我应该移动extern "C"
位吗?特别是看到在实践中,这些标头中的一些标头本身可能具有extern "C"
吗?
NO,一般情况下,您不应该将其移动到包含标头。
extern "C"
用于指示函数正在使用C调用约定。声明对变量和#define
s没有影响,因此不需要包含这些。如果#include
在extern "C"
块内,这将有效地修改该头文件内的函数声明
背景:在没有extern "C"
声明的情况下,当使用C编译器编译时,假定函数遵循C约定,而当使用C++编译器编译时则假定遵循C++约定。如果C和C++代码使用相同的头文件,则会出现链接器错误,因为编译后的函数在C和C++中有不同的名称。
虽然可以将所有代码放在#ifdef块之间,但我个人不喜欢它,因为它实际上只适用于函数原型,我经常看到人们将它复制粘贴到不应该粘贴的地方。最干净的方法是将它放在应该粘贴的位置,即在C/C++头文件中的函数原型周围。
所以,为了回答你的问题";在包含标头include指令之前,我应该移动extern "C"
位吗&";,我的回答是:不,你不应该。
但这可能吗?是的,在很多情况下,这不会破坏任何东西。有时,如果外部头文件中的函数原型不正确(例如,当它们是C函数并且您想从C++调用它们时),并且您无法更改该库,则这是必要的。
然而,也有这样做会破坏构建的情况。下面是一个简单的例子,如果使用extern "C"
:包装include,则编译失败
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;
}
main.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 main无论哪种方式都会构建良好,因为它不受#ifdef __cplusplus
块的影响。
令人惊讶的是。在阅读了标准之后,现在甚至我也会写
#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链接[使用.链接]状态
使用外部链接声明的C标准库中的名称是否具有外部"C";或外部";C++";链接是由实现定义的。是的建议实现使用extern";C++";链接目的.1
为了安全起见,您确实应该将extern "C"
包裹在这些include周围,但您不应该这样做,因为这意味着D.9 C headers[dep.C.headers]中的headers,如您正在使用的<stdlib.h>
。
这在C++常见问题解答中有介绍。
首先,如何在C++代码中包含标准C头文件不需要什么特别的东西,因为标准的C头文件可以与C++无缝地工作。因此,您不需要将stdarg.h
和stddef.h
封装在extern "C"
中。
然后,对于非标准C标头,有两种可能性:要么不能更改标头,要么可以修改标头。
如果无法更改C标头,请将#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
本身。
无论如何/何时/何地包含标头,标头都应以相同的方式工作。将#include
封装在extern "C"
、#pragma pack
、特殊#define
等中,应保留最后的解决方案,因为这可能会干扰标头在不同场景中的行为,从长远来看会降低系统的可维护性。
正如StoryTeller在评论中所说:
一些报头是在假设最外面的";"范围";具有C++语言链接。他们可能会使用
__cplusplus
来删除模板声明,如果你想把它们包装起来,你的头就会从根本上被破坏。因此,如前所述,使您的声明正确,并让其他标头不受阻碍地执行它们的操作。即使是标准的库头也可能会中断(因为实现者可能会认为它无论如何都会被共享,然后在内部做一些专家友好的事情)。
请注意,标准C标头可以使用C链接实现,但也可以使用C++链接。在这种情况下,将它们封装在extern "C"
中可能会导致链路错误。