如何在C++项目中使用C源文件?

31
在C++项目中,包含C源文件的.h文件会导致许多错误,因为C和C++之间存在不同的标准。
如何在C++项目(或main.cpp中)使用C源文件?

6
使用C编译器编译C源代码;使用C++编译器编译C++源代码(最好在C++中编写“main()”函数);使用C++编译器将程序链接起来。 - Jonathan Leffler
1
你能详细说明一下你尝试了什么以及出现了什么错误吗? - Karthik T
2
不兼容性很少见。如果您想在C++编译器中使用它们,您需要“修复”C文件以使其与C++兼容,或者可以单独链接C对象文件。我个人遇到的唯一问题是C中将void*隐式转换为任何其他指针类型。 - Ed S.
使用它们的方式与cpp文件相同;不要忘记使用extern "C"包含头文件。 - Sergey Kalinichenko
1
@KuramaYoko 在 C 语言中有很多完全合法(甚至是惯用的)结构,在 C++ 编译器中会抛出错误。即使 int *i = malloc(sizeof(int) * 10); 在 C++ 中也是非法的。任何使用 class 作为变量名或使用 c99 动态数组等代码都是非法的。 - dmckee --- ex-moderator kitten
显示剩余5条评论
4个回答

45

为达到最大可靠性:

  • 用 C 编译器编译 C 源代码。
  • 用 C++ 编译器编译 C++ 源代码。
  • 最好在 C++ 中编写 main() 函数。
  • 用 C++ 编译器链接程序。

确保 C 头文件本身要么知晓 C++,要么将 C 头文件包含在 extern "C" { ... } 块内的 C++ 代码中。

或者 (C 头文件 cheader.h):

#ifndef CHEADER_H_INCLUDED
#define CHEADER_H_INCLUDED

#ifdef __cplusplus
extern "C" {
#endif

...main contents of header...

#ifdef __cplusplus
}
#endif

#endif /* CHEADER_H_INCLUDED */ 

或者 (C++源代码):

extern "C" {
#include "cheader.h"
}

现代C语言的风格与C和C++语言的公共子集非常接近。但是,由于许多原因,任意的C代码都不是C++代码,仅仅通过更改扩展名或者使用C++编译器编译C源文件来调用C源文件作为C++源文件并不保证成功。通常情况下,将C编译为C,将C++编译为C++,然后使用C++编译器链接生成的目标文件(以确保正确的支持库被调用)更容易。

然而,如果MSVC编译器说使用MFC的程序必须完全用C++编写(“MFC需要C++编译(使用.cpp后缀)”是报告的错误),那么您可能别无选择,只能确保您的C代码可以编译为C++代码。这意味着您必须对malloc()等函数的返回值进行强制类型转换,必须关注其他地方是否未使用强制类型转换将void *转换为其他指针类型,必须关注C中sizeof('a') == 4而在C++中sizeof('a') == 1,必须确保每个函数在使用之前都被声明,必须确保您的C代码不使用任何C++关键字(尤其是typenameclass,有时还有inline - 但是完整的列表相当大)。

在某些圈子中,您可能需要担心C99中的一些特性,在C++2003或C++2011中不存在,例如灵活数组成员、指定初始化器、复合文字、可变长度数组等等。然而,如果C代码是为MSVC编写的,则可能不会出现问题;这些特性不受MSVC C编译器的支持(它仅支持C89,而不支持C99)。

顺便说一句:我有一个脚本来查找C++关键字。它包含以下注释:

# http://en.cppreference.com/w/cpp/keywords
# plus JL annotations
# and                               C (<iso646.h>)
# and_eq                            C (<iso646.h>)
# alignas (C++11 feature)
# alignof (C++11 feature)
# asm                               C (core)
# auto(1)                           C (core)
# bitand                            C (<iso646.h>)
# bitor                             C (<iso646.h>)
# bool                              C99 (<stdbool.h>)
# break                             C (core)
# case                              C (core)
# catch
# char                              C (core)
# char16_t (C++11 feature)
# char32_t (C++11 feature)
# class
# compl                             C (<iso646.h>)
# const                             C (core)
# constexpr (C++11 feature)
# const_cast
# continue                          C (core)
# decltype (C++11 feature)
# default(1)                        C (core)
# delete(1)
# double                            C (core)
# dynamic_cast
# else                              C (core)
# enum                              C (core)
# explicit
# export
# extern                            C (core)
# false                             C99 (<stdbool.h>)
# float                             C (core)
# for                               C (core)
# friend
# goto                              C (core)
# if                                C (core)
# inline                            C (core)
# int                               C (core)
# long                              C (core)
# mutable
# namespace
# new
# noexcept (C++11 feature)
# not                               C (<iso646.h>)
# not_eq                            C (<iso646.h>)
# nullptr (C++11 feature)
# operator
# or                                C (<iso646.h>)
# or_eq                             C (<iso646.h>)
# private
# protected
# public
# register                          C (core)
# reinterpret_cast
# return                            C (core)
# short                             C (core)
# signed                            C (core)
# sizeof                            C (core)
# static                            C (core)
# static_assert (C++11 feature)
# static_cast
# struct                            C (core)
# switch                            C (core)
# template
# this
# thread_local (C++11 feature)
# throw
# true                              C99 (<stdbool.h>)
# try
# typedef                           C (core)
# typeid
# typename
# union                             C (core)
# unsigned                          C (core)
# using(1)
# virtual
# void                              C (core)
# volatile                          C (core)
# wchar_t                           C (core)
# while                             C (core)
# xor                               C (<iso646.h>)
# xor_eq                            C (<iso646.h>)

(1) 后缀是 CPP Reference 上的一个脚注:

  • (1) — 意义在 C++11 中发生了改变

MFC 需要进行 C++ 编译(使用 .cpp 后缀)。 - Al2O3
那么你终究不能使用C,因为微软不允许。很遗憾,你必须确保你的C代码也是C++代码。这比将你的C代码转换成C代码更难。祝你好运,或者选择另一个操作系统。(我不知道这个错误信息是否意味着你不能使用“extern C”符号;我没有用过MSVC和MFC编码。)请注意,如果你在问题中提到了平台,你可能会立即得到更好的答案,而我可能根本不会尝试回答。 - Jonathan Leffler
跟进问题,如果您不介意@JonathanLeffler:[使用cmake构建C++项目,如何在CMakeLists.txt中指定构建一些简单的C文件(在第三方子目录中,如果是C++项目)?](https://dev59.com/Zqrka4cB1Zd3GeqPlfHB) - BoltzmannBrain

12

从C++示例中提取最小可运行的C代码

从C++调用C函数非常简单:每个C函数只有一个可能的非重载符号,因此不需要额外的工作。

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

c.h

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

#include "c.h"

int f() { return 1; }

跑:
g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

我在这里更详细地解释了extern "C"

GitHub上的示例

从C示例中最小的可运行C++代码

从C++调用有点困难:我们必须手动创建每个要公开的函数的非重载版本。

这里我们演示如何将C++函数重载公开给C。

main.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.h

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

跑:
gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

GitHub上的示例


2

C++强调对C源代码的“向后兼容性”,因此可以将C源代码复制到.cpp文件中进行构建。 现在,C++并非完全向后兼容,因此您可能需要在C源代码中进行一些更改,但通常情况下,仅会出现最少量的错误。只需确保包含.c使用的C库(考虑到您的编译器也支持C语言)即可。

#include <stdio.h>
#include <string.h>
//so on

0

如果你只是使用源代码而不是一些预编译库,在大多数情况下,你可以将 .c 文件重命名为 .cpp 文件。


但是一些在C文件的.h中定义的全局变量会导致许多错误,以及其他更多的错误。 - Al2O3
这就是为什么我说在大多数情况下,但你是对的... 全局变量无论如何都不是一个好的实践呵呵呵 - Salchi13
1
重命名.c文件只有在C代码很烂的情况下才有效:这要求C实际上在C++的公共子集中,而对于好的C代码来说并非如此。例如,C程序员malloc()的结果强制转换是有很好的理由的,而C++则强制转换。C具有真正的多维动态数组,而C++没有。好的C代码还有一些C++不允许的东西,这只是我脑海中的两个例子。 - cmaster - reinstate monica

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