ChatGPT解决这个技术问题 Extra ChatGPT

C++中extern "C" 的作用是什么?

extern "C" 放入 C++ 代码到底有什么作用?

例如:

extern "C" {
   void foo();
}
我想向您介绍这篇文章:http://www.agner.org/optimize/calling_conventions.pdf 它告诉您更多关于调用约定和编译器之间的区别。

a
a stone arachnid

extern "C" 使 C++ 中的函数名称具有 C 链接(编译器不会破坏名称),以便客户端 C 代码可以使用仅包含函数声明的 C 兼容头文件链接到(使用)您的函数。您的函数定义包含在二进制格式(由您的 C++ 编译器编译)中,然后客户端 C 链接器将使用 C 名称链接到该格式。

由于 C++ 有函数名重载,而 C 没有,C++ 编译器不能只使用函数名作为链接的唯一 id,因此它通过添加有关参数的信息来破坏名称。 AC 编译器不需要修改名称,因为您不能在 C 中重载函数名称。当您在 C++ 中声明函数具有 extern "C" 链接时,C++ 编译器不会将参数/参数类型信息添加到用于链接的名称中.

如您所知,您可以显式指定与每个单独的声明/定义的 extern "C" 链接,或使用块对一系列声明/定义进行分组以具有特定链接:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

如果您关心技术细节,它们在 C++03 标准的第 7.5 节中列出,这里是一个简短的总结(重点是 extern "C"):

extern "C" 是一个链接规范

每个编译器都需要提供“C”链接

链接规范应仅出现在命名空间范围内

所有函数类型、函数名和变量名都有语言链接参见 Richard 的评论:只有具有外部链接的函数名和变量名才有语言链接

具有不同语言链接的两个函数类型是不同的类型,即使其他方面相同

联动规格嵌套,内一决定最终联动

类成员忽略 extern "C"

至多一个具有特定名称的函数可以具有“C”链接(无论命名空间如何)

extern "C" 强制函数具有外部链接(不能使其成为静态)参见 Richard 的评论:extern "C" 内部的静态是有效的;如此声明的实体具有内部链接,因此没有语言链接

从 C++ 到用其他语言定义的对象以及从其他语言到用 C++ 定义的对象的链接是实现定义和语言相关的。只有在两种语言实现的对象布局策略足够相似的情况下,才能实现这样的联动


编译器不使用 c++ 所使用的修饰。因此,如果您想从 c++ 程序中调用 ac 接口,则必须清楚地将 c 接口声明为“extern c”。
@Faisal:不要尝试链接使用不同 C++ 编译器构建的代码,即使交叉引用都是'extern "C"'。类的布局、用于处理异常的机制、用于确保变量在使用前初始化的机制或其他此类差异之间经常存在差异,另外您可能需要两个单独的 C++ 运行时支持库(一个用于每个编译器)。
'extern "C" 强制函数具有外部链接(不能使其成为静态)'是不正确的。 'extern "C"' 中的 'static' 有效;如此声明的实体具有内部链接,因此没有语言链接。
“所有函数类型、函数名和变量名都有语言链接”也是不正确的。只有具有外部链接的函数名和变量名具有语言链接。
请注意,extern "C" { int i; } 是一个定义。这可能不是您想要的,在 void g(char); 的非定义旁边。要使其成为非定义,您需要 extern "C" { extern int i; }。另一方面,不带大括号的单声明语法确实使声明成为非定义:extern "C" int i;extern "C" { extern int i; } 相同
U
UncaAlby

只是想添加一些信息,因为我还没有看到它发布。

你会经常在 C 头文件中看到这样的代码:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

这样做的目的是允许您在 C++ 代码中使用该 C 头文件,因为将定义宏“__cplusplus”。但是您仍然可以将它与未定义宏的遗留 C 代码一起使用,因此它不会看到唯一的 C++ 构造。

虽然,我也看过 C++ 代码,例如:

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

我想这可以完成同样的事情。

不知道哪种方式更好,但我都见过。


有明显的区别。在前者的情况下,如果你用普通的 gcc 编译器编译这个文件,它会生成一个函数名没有被破坏的对象。如果您随后使用链接器链接 C 和 C++ 对象,它将找不到函数。您将需要在第二个代码块中包含这些带有 extern 关键字的“旧头”文件。
@Anne:C++ 编译器也会查找未损坏的名称,因为它在标题中看到了 extern "C" )。效果很好,多次使用这种技术。
@Anne:不对,第一个也很好。它被 C 编译器忽略,与 C++ 中的第二个效果相同。编译器不在乎它是否在包含标头之前或之后遇到 extern "C"。当它到达编译器时,无论如何它只是一个长长的预处理文本流。
@Anne,不,我认为您已经受到源中其他一些错误的影响,因为您所描述的内容是错误的。至少在过去 17 年的任何时候,对于任何目标,没有任何版本的 g++ 出错。第一个示例的重点是,无论您使用 C 还是 C++ 编译器,都不会对 extern "C" 块中的名称进行名称修改。
“哪个更好” - 当然,第一个变体更好:它允许在 C 和 C++ 代码中直接包含标头,而无需任何进一步的要求。第二种方法是针对 C 标头的一种解决方法,作者忘记了 C++ 保护(没问题,不过,如果之后添加这些,则接受嵌套的 extern "C" 声明......)。
C
Ciro Santilli Путлер Капут 六四事

反编译 g++ 生成的二进制文件以查看发生了什么

主文件

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

编译并反汇编生成的 ELF 输出:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

输出包含:

     8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
     9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef
    10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

解释

我们看到:

ef 和 eg 存储在与代码中同名的符号中

其他符号被破坏。让我们解开它们: $ c++filt _Z1fv f() $ c++filt _Z1hv h() $ c++filt _Z1gv g()

结论:以下两种符号类型均未损坏:

定义

已声明但未定义 (Ndx = UND),在链接或运行时从另一个目标文件提供

因此,调用时您将需要 extern "C"

C 来自 C++:告诉 g++ 期待由 gcc 生成的未损坏符号

C++ 来自 C:告诉 g++ 生成未损坏的符号供 gcc 使用

在外部 C 中不起作用的东西

很明显,任何需要名称修改的 C++ 功能都不能在 extern C 中工作:

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

来自 C++ 示例的最小可运行 C

为了完整起见,对于新人,另请参阅:How to use C source files in a C++ project?

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

主文件

#include <cassert>

#include "c.h"

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

ch

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++ 
 * because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

抄送

#include "c.h"

int f(void) { 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",链接将失败,并显示:

main.cpp:6: undefined reference to `f()'

因为 g++ 期望找到一个损坏的 f,而 gcc 没有产生。

Example on GitHub

C 示例中的最小可运行 C++

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

在这里,我们说明了如何将 C++ 函数重载暴露给 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

如果没有 extern "C",它会失败:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

因为 g++ 生成了 gcc 找不到的错位符号。

Example on GitHub

当我包含来自 C++ 的 C 标头时,extern "c" 在哪里?

像 cstdio 这样的 C 标头的 C++ 版本可能依赖于 #pragma GCC system_header,其中 https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html 提到:“在某些目标上,例如 RS/6000 AIX、GCC当编译为 C++ 时,隐式地用“extern“C”块包围所有系统头文件。”,但我没有完全确认。

/usr/include/unistd.h 之类的 POSIX 标头包含在:Do I need an extern "C" block to include standard POSIX C headers?通过 __BEGIN_DECLS,在 Ubuntu 20.04 上复制。 __BEGIN_DECLS 通过#include 包含在内。

在 Ubuntu 18.04 中测试。


最佳答案,因为您 1) 明确提到 extern "C" { 可以帮助您从 C++ 程序中调用未修改的 C 函数,以及从 C 程序中调用 未修改的 C++ 函数,其他 2)的答案并不那么明显,因为您展示了每个的不同示例。谢谢!
我想知道像 unistd.h、sys/stat.h 和 sys.types.h 这样的 C 头文件。他们似乎没有将“'C'”放在“extern”之后。从 C++ 代码中使用它们似乎仍然没有问题。是因为这些是没有实现文件的纯标头吗?
@Paul 他们似乎使用宏 __BEGIN_DECLS 启用了 extern C:stackoverflow.com/questions/8087438/… 我观察了 Ubuntu 20.04 上 unistd.h 的答案中提到的内容。但是,对于 cstdio,它可能依赖于 #pragma GCC system_headergcc.gnu.org/onlinedocs/cpp/System-Headers.html
谢谢!奇怪的是,当我搜索时,这个问题没有出现,现在当我搜索那个特定的宏时,id 出现了......我想在这里链接它是件好事。由于 __BEGIN_DECLS 是在 sys/cdefs.h 中定义的,但这并不包含在 unistd.h、sys/stat.h 和 sys/types.h 中,我猜 sys/cdefs.h 默认情况下只是包含在预处理器中?
@Paul 不用担心,我们都在谷歌上帝的意志下生死。它通过 #include <features.h> 包含在内。
E
Error

在每个 C++ 程序中,所有非静态函数在二进制文件中都表示为符号。这些符号是在程序中唯一标识一个函数的特殊文本字符串。

在 C 中,符号名称与函数名称相同。这是可能的,因为在 C 中没有两个非静态函数可以具有相同的名称。

因为 C++ 允许重载并且有许多 C 不具备的特性——比如类、成员函数、异常规范——不可能简单地使用函数名作为符号名。为了解决这个问题,C++ 使用了所谓的名称修饰,它将函数名称和所有必要的信息(如参数的数量和大小)转换为一些看起来很奇怪的字符串,只能由编译器和链接器处理。

因此,如果您将函数指定为 extern C,编译器不会对其执行名称修改,并且可以使用其符号名称作为函数名称直接访问它。

这在使用 dlsym()dlopen() 调用此类函数时很方便。


方便是什么意思?符号名称 = 函数名称是否会使符号名称传递给 dlsym 已知,或者其他什么?
@错误:是的。在一般情况下,dlopen() 仅给定头文件的 C++ 共享库并选择要加载的正确函数基本上是不可能的。 (在 x86 上,有一个以 Itanium ABI 形式发布的名称修饰规范,我知道的所有 x86 编译器都使用它来修饰 C++ 函数名称,但该语言中没有任何东西需要这个。)
t
tim-montague

C++ 修改函数名称以从过程语言创建面向对象的语言

大多数编程语言都不是建立在现有编程语言之上的。 C++ 建立在 C 之上,而且它是一种从过程编程语言构建的面向对象的编程语言,因此有像 extern "C" 这样的 C++ 表达式提供与 C 的向后兼容性。

让我们看下面的例子:

#include <stdio.h>
    
// Two functions are defined with the same name
//   but have different parameters

void printMe(int a) {
  printf("int: %i\n", a);
}

void printMe(char a) {
  printf("char: %c\n", a);
}
    
int main() {
  printMe('a');
  printMe(1);
  return 0;
}

AC 编译器不会编译上面的例子,因为同一个函数 printMe 被定义了两次(即使它们有不同的参数 int achar a)。

gcc -o printMe printMe.c && ./printMe; 1 个错误。 PrintMe 被多次定义。

但是,C++ 编译器将编译上述示例。它不关心 printMe 被定义了两次。

g++ -o printMe printMe.c && ./printMe;

这是因为 C++ 编译器会根据函数的参数隐式重命名 (mangles) 函数。该语言被设计为面向对象的 - 使用相同名称的方法(函数)创建不同的类,并根据不同的参数覆盖方法名称(method overriding)。

extern "C" 说的是“不要破坏 C 函数名”

尽管 C++ 是基于 C 构建的,但修改可能会导致 C 代码混乱。例如,假设我们有一个名为“parent.c”的遗留 C 文件,include它的函数名称来自不同的头文件、“parent.h”、“child.h”等。如果我们运行“parent.c”通过 C++ 编译器,它将破坏该文件中的函数名,并且它们将不再与头文件中指定的函数名匹配。因此,“parent.h”和“child.h”头文件中的函数名也需要被修改。这对于一些文件可能没问题,但如果 C 程序很复杂,修改可能会很慢并导致代码损坏,因此提供一个告诉 C++ 编译器不要修改函数名称的关键字可能会很方便。

extern "C" 关键字告诉 C++ 编译器不要破坏(重命名)C 函数名称。

例如:

extern "C" void printMe(int a);


如果我们只有一个 dll 文件,我们可以不使用 extern "C" 吗?我的意思是如果我们没有头文件而只有一个源文件(只是实现)并通过函数指针使用它的函数。在这种状态下,我们只是使用了函数(不管它的名字)。
M
MarcH

仅通过包装在 extern "C" 中,不能使任何 C 头文件与 C++ 兼容。当 C-header 中的标识符与 C++ 关键字冲突时,C++ 编译器会抱怨这一点。

例如,我在 g++ 中看到以下代码失败:

extern "C" {
struct method {
    int virtual;
};
}

有点道理,但是在将 C 代码移植到 C++ 时要牢记这一点。


extern "C" 表示使用 C 链接,如其他答案所述。这并不意味着“将内容编译为 C”或任何东西。 int virtual; 在 C++ 中无效,并且指定不同的链接不会改变这一点。
...或模式通常,任何包含语法错误的代码都不会编译。
@ValentinHeinitz 自然而然,尽管在 C 中使用“虚拟”作为标识符并不是语法错误。我只是想指出,您不能通过在其周围放置 extern "C" 来自动使用 C++ 中的任何 C 标头。
我刚刚遇到了一个不同的兼容性问题。 C 标头在某些结构的 typedef 上使用了结构前缀。它使用 gcc 和 clang 在 -Wextra 上编译时没有错误或警告,但使用 g++ 和 clang++ 编译失败,因为 struct 只允许用于原始标识符,而不是它的 typedef。我必须修改标头以使其与 extern "C" {...} 包装器之外的 C++ 兼容,现在它可以在 C 和 C++ 版本上编译。
E
Employed Russian

它改变了函数的链接,使得函数可以从 C 中调用。实际上,这意味着函数名不是 mangled


Mangled 是通常使用的术语......不要相信我曾经见过'decorated'与这个含义一起使用。
微软(至少部分地)使用 decorated 而不是在他们的文档中被破坏。他们甚至将他们的工具命名为 undecorate(又名 un-mangle)一个名称 undname
M
Mark Rushakoff

它通知 C++ 编译器在链接时以 C 风格查找这些函数的名称,因为在链接阶段用 C 和 C++ 编译的函数的名称是不同的。


J
Jonathan Leffler

extern "C" 旨在被 C++ 编译器识别并通知编译器指出的函数已(或将要)以 C 样式编译,以便在链接时链接到 C 函数的正确版本。


Y
Yogeesh H T

extern "C" 是一种链接规范,用于在 Cpp 源文件调用 C 函数。我们可以调用C函数,编写变量,&包括标题。函数在外部实体 & 中声明它是在外面定义的。语法是

类型 1:

extern "language" function-prototype

类型 2:

extern "language"
{
     function-prototype
};

例如:

#include<iostream>
using namespace std;

extern "C"
{
     #include<stdio.h>    // Include C Header
     int n;               // Declare a Variable
     void func(int,int);  // Declare a function (function prototype)
}

int main()
{
    func(int a, int b);   // Calling function . . .
    return 0;
}

// Function definition . . .
void func(int m, int n)
{
    //
    //
}

S
SturmCoder

我之前对 dll(动态链接库)文件使用了 'extern "C"' 来使等 main() 函数“可导出”,因此以后可以在 dll 的另一个可执行文件中使用它。也许我曾经使用它的一个例子很有用。

动态链接库

#include <string.h>
#include <windows.h>

using namespace std;

#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
    MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}

EXE文件

#include <string.h>
#include <windows.h>

using namespace std;

typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder

int main()
{
    char winDir[MAX_PATH];//will hold path of above dll
    GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
    strcat(winDir,"\\exmple.dll");//concentrate dll name with path
    HINSTANCE DLL = LoadLibrary(winDir);//load example dll
    if(DLL==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if load fails exit
        return 0;
    }
    mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
    //defined variable is used to assign a function from dll
    //GetProcAddress is used to locate function with pre defined extern name "DLL"
    //and matcing function name
    if(mainDLLFunc==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if it fails exit
        return 0;
    }
    mainDLLFunc();//run exported function 
    FreeLibrary((HMODULE)DLL);
}

虚假。 extern "C"__declspec(dllexport) 不相关。前者控制符号装饰,后者负责创建导出条目。您也可以使用 C++ 名称修饰来导出符号。除了完全忽略这个问题的重点之外,代码示例中还存在其他错误。一方面,从您的 DLL 导出的 main 没有声明返回值。或者调用约定,就此而言。导入时,您指定了一个随机调用约定 (WINAPI),并为 32 位构建使用了错误的符号(应该是 _main_main@0)。对不起,-1。
这只是重复,你不知道你在做什么,但这样做似乎对你有用,对于一些未公开的目标平台列表。你没有解决我在之前的评论中提出的问题。由于大错特错,这仍然是一个否决票(还有更多,不适合单个评论)。
在 Stack Overflow 上发布答案意味着您知道自己在做什么。这是意料之中的。至于您尝试“防止运行时堆栈损坏”:您的函数签名指定了类型为 void* 的返回值,但您的实现不返回任何内容。那会飞得真好...
如果您实现了某些似乎可行的东西,纯属运气,那么您显然不知道自己在做什么(您的“工作”示例属于该类别)。这是未定义的行为,看起来有效是未定义行为的一种有效形式。它仍然未定义。如果您以后更加勤奋,我将不胜感激。其中一部分可能是删除这个提议的答案。
您正在将不返回任何内容的函数重新解释为返回指针的函数。纯属幸运,x86 对不匹配的函数签名非常宽容,尤其是整数类型的返回值。您的代码只是巧合。如果你不同意,你需要解释为什么你的代码可以可靠地工作。
M
Manohar Reddy Poreddy

这个答案是为急躁/有截止日期的人准备的,下面只有一部分/简单的解释:

在 C++ 中,您可以通过重载在类中具有相同的名称(例如,由于它们都是相同的名称,因此不能从 dll 中按原样导出等)解决这些问题的方法是将它们转换为不同的字符串(称为符号),符号说明函数的名称,也包含参数,因此这些函数中的每一个即使具有相同的名称,也可以唯一标识(也称为名称修饰)

在 C 中,您没有重载,函数名称是唯一的(因此,不需要用于唯一标识函数名称的单独字符串,因此符号是函数名称本身)

所以在 C++ 中,在 C 中使用名称修饰唯一标识每个函数,即使没有名称修饰唯一标识每个函数

要更改 C++ 的行为,即指定不应为特定函数发生名称修改,无论出于何种原因,您都可以在函数名称前使用 extern "C",例如从 dll 导出具有特定名称的函数,供其客户使用。

阅读其他答案,以获得更详细/更正确的答案。


g
gnasher729

C编译器编译的函数void f()和C++编译器编译的同名函数void f()不是同一个函数。如果您在 C 中编写了该函数,然后您尝试从 C++ 调用它,那么链接器将查找 C++ 函数但找不到 C 函数。

extern "C" 告诉 C++ 编译器你有一个由 C 编译器编译的函数。一旦你告诉它它是由 C 编译器编译的,C++ 编译器就会知道如何正确调用它。

它还允许 C++ 编译器以 C 编译器可以调用它的方式编译 C++ 函数。该函数将正式成为 C 函数,但由于它是由 C++ 编译器编译的,因此它可以使用所有 C++ 功能并具有所有 C++ 关键字。


C++ 编译器可以编译 extern "C" 函数,并且(受某些约束)它可以被 C 编译器编译的代码调用。
B
Bhargav Rao

当混合 C 和 C++ 时(即 a. 从 C++ 调用 C 函数;和 b. 从 C 调用 C++ 函数),C++ 名称修改会导致链接问题。从技术上讲,这个问题只有在被调用函数已经使用相应的编译器编译成二进制文件(很可能是 *.a 库文件)时才会发生。

所以我们需要使用 extern "C" 来禁用 C++ 中的名称修饰。


J
Jonathan Leffler

在不与其他好的答案冲突的情况下,我将添加一些我的示例。

C++ Compiler 究竟做了什么:它在编译过程中破坏了名称,因此我们需要告诉编译器专门treat C 实现。

当我们创建 C++ 类并添加 extern "C" 时,我们是在告诉 C++ 编译器我们正在使用 C 调用约定。

原因(我们从 C++ 调用 C 实现):要么我们想从 C++ 调用 C 函数,要么从 C 调用 C++ 函数(C++ 类......等在 C 中不起作用)。


欢迎来到堆栈溢出。如果您决定回答一个已确定且正确答案的旧问题,那么在当天晚些时候添加新答案可能不会让您获得任何荣誉。如果您有一些独特的新信息,或者您确信其他答案都是错误的,请务必添加一个新答案,但是在提出问题很长时间后提供相同基本信息的“另一个答案”通常不会不会为你赢得太多荣誉。坦率地说,我认为这个答案没有什么新东西。
J
Jonathan Leffler

请参阅下面的链接,这是 Geeks for Geeks 对 extern "C" 用法的解释。从下面的页面添加重要信息。

考虑以下函数 f() 的声明

int  f (void) { return 1; }
int  f (int)  { return 0; }
void g (void) { int i = f(), j = f(0); }

C++ 编译器可能会将上述名称更改为以下名称(来源:Wiki)

int  __f_v (void) { return 1; }
int  __f_i (int)  { return 0; }
void __g_v (void) { int i = __f_v(), j = __f_i(0); }

https://www.geeksforgeeks.org/extern-c-in-c/


虽然链接可能会回答问题,但规则要求答案是自给自足的,以防链接停止工作。您可以将链接中的要点添加到您的答案中吗?请参阅stackoverflow.com/help/how-to-answer
@HolyBlackCat,将满足需求。