首页 > 解决方案 > extern "C" - 在库头包含之前还是之后?

问题描述

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

标签: c++cincludecross-languageextern-c

解决方案


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

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块的影响。


推荐阅读