Extern “C“ 链接指示 & 函数重载

Extern “C” 链接指示 & 函数重载

用于在 cpp 代码中调用 c 语言代码

1. 测试代码,cpp 文件中调用 c 代码

test.h 文件

#pragma once
#include<stdio.h>

void show();

test.c 文件

#include "test.h"

void show()
{
	printf("hello world CCCC\n");
}

ex01.cpp 文件

#include<iostream>
#include "test.h"

using namespace std;

int main()
{
	show();
	return 0;
}

2. 编译测试

2.1 报错

windows环境 :vs2019

编译通过,但是无法执行,

LNK1120 1 个无法解析的外部命令

LNK2019 无法解析的外部符号 “void __cdecl show(void)” (? show@@YAXXZ),函数 _main 中引用了该符号 test.obj

linux环境 :gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1

$ g++ -c ex01.cpp  # 按照 c++ 编译     通过    生成 ex01.o 文件
$ gcc -c test.c    # 按照 c 编译       通过    生成 test.o 文件
$ g++ test.o ex01.o -o exec01   # 报错   test.o 和 ex01.o 顺序可换

/usr/bin/ld: ex01.o: in function `main':
ex01.cpp:(.text+0x9): undefined reference to `show()'
collect2: error: ld returned 1 exit status

按照 vs2019 的 报错信息,无法解析,大概是链接时找不到方法或者链接不到函数

vs2019gcc 都将错误指向了 show() 方法,但是两者都能正常编译,应该是链接出现了问题。

26 extern C浅析_

原因:C++ 为实现函数重载(c 语言不支持函数重载),使用了和 C 不同的函数命名,同样的 show() 函数,在 ex01.cpp 和 test.c 中使用了不同的命名方式,所以链接时,ex01.cpp 按照 cpp 的命名结果去找 show() ,而 test.c 的 show() 不符合 cpp 的要求 (两者名字是不一样的), 所以 出现找不到 show() 方法的情况

具体看 4 汇编

2.2 使用 Extern “C” 解决

在 ex01.cpp 中对 show() 方法使用 extern “C” 修饰,告诉编译器 show() 不再按照 cpp 方式处理,而是按照 c 语言方式处理

2.2.1 方法一:修改 ex01.cpp 文件
#include<iostream>
// #include "test.h"

using namespace std;

//C++ 中调用 C语言方法
extern "C" void show();   // 使用 C语言方式链接 show() 方法

int main()
{
	show();
	return 0;
}

注意要注释 #include “test.h” , 否则会重复声明 show

vs2019 下直接运行

linux 下要重新编译 ex01.cpp

$ g++ -c ex01.cpp
$ g++ test.o ex01.o -o exec01
$ ./exec01 
# 输出
hello world CCCC
2.2.2 方法二:修改 test.h 文件, 使用条件编译
#pragma once
#include<stdio.h>

#ifdef __cplusplus  //使用条件编译,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码
extern "C"
#endif

void show();  // show() 受到 extern "C" 的影响

void show2()  // show2() 不受 extern "C" 的影响

注意上述的条件编译,只能作用 #endif 后面的第一个 函数,

ex01.cpp 保持不变

#include<iostream>
#include "test.h"

using namespace std;

//C++ 中调用 C语言方法
// extern "C" void show();   // 使用 C语言方式链接 show() 方法

int main()
{
	show();
	return 0;
}

linux gcc 下需要重新编译 test.c

3. CPP 中引用多个 C 函数

定义多个 show() 函数

test.h

#pragma once
#include<stdio.h>

void show();

void show2(int a);

void show3(int a, double b);

void show4(int a, int b, double c);

test.c

#include "test.h"

void show()
{
	printf("hello world CCCC\n");
}

void show2(int a)
{
	printf("hello world CCCC %d\n", a);
}

void show3(int a, double b)
{
	printf("hello world CCCC %f\n", a + b);
}

void show4(int a, int b, double c)
{
	printf("hello world CCCC %f\n", a + b + c);
}

3.1 方法一:ex01.cpp 中对每一个 C 函数 使用 extern “C”

ex01.cpp

#include<iostream>
//#include "test.h"

using namespace std;

//C++ 中调用 C语言方法
extern "C" void show();   // 使用 C语言方式链接 show() 方法
extern "C" void show2(int a);
extern "C" void show3(int a, double b);
extern "C" void show4(int a, int b, double c);

int main()
{
	show();
	show2(1);
	show3(1, 2.0);
	show4(1, 2, 3.0);
	return 0;
}

注意要注释 #include “test.h” , 否则会重复声明 show

这里给出 linux 下的结果

$ gcc -c test.c
$ g++ -c ex01.cpp 
$ g++ test.o ex01.o -o exec01
$ ./exec01
hello world CCCC
hello world CCCC 1
hello world CCCC 3.000000
hello world CCCC 6.000000

ex01.cpp也可以使用 复合 的方式,一个 extern “C” 作用多个函数

#include<iostream>
//#include "test.h"

using namespace std;

extern "C"
{
	void show();
	void show2(int a);
	void show3(int a,double b);
	void show4(int a,int b,double c);
}
int main()
{
	show();
	show2(1);
	show3(1, 2.0);
	show4(1, 2, 3.0);
	return 0;
}

3.2 方法二: 在 test.h 中使用条件编译

注意,类似 2.2.2, 一个 extern “C” 只能作用一个函数,所以也要用 复合 的方式进行处理

test.h

#pragma once
#include<stdio.h>

#ifdef __cplusplus  //条件编译 __cplusplus是cpp中的自定义宏
extern "C"{
#endif
void show();

void show2(int a);

void show3(int a, double b);

void show4(int a, int b, double c);

#ifdef __cplusplus
}
#endif

ex01.cpp

#include<iostream>
#include "test.h"

using namespace std;

int main()
{
	show();
	show2(1);
	show3(1, 2.0);
	show4(1, 2, 3.0);
	return 0;
}

4. linux 查看汇编结果

用 vs2019 查看也可以,比较麻烦

使用以下测试代码

test.h

#pragma once
#include<stdio.h>

void show();

void show2(int a);

void show3(int a, double b);

void show4(int a, int b, double c);

test.c

#include "test.h"

void show()
{
	printf("hello world CCCC\n");
}

void show2(int a)
{
	printf("hello world CCCC %d\n", a);
}

void show3(int a, double b)
{
	printf("hello world CCCC %f\n", a + b);
}

void show4(int a, int b, double c)
{
	printf("hello world CCCC %f\n", a + b + c);
}

ex01.cpp

#include<iostream>
#include "test.h"

using namespace std;

int main()
{
	show();
	show2(1);
	show3(1, 2.0);
	show4(1, 2, 3.0);
	return 0;
}

linux下分别编译 ex01.cpptest.c , 可以知道上面的代码可以编译,但是无法链接得到可以执行文件 (没有使用 extern “C”)

$ gcc -c test.c
$ g++ -c ex01.cpp 
$ g++ test.o ex01.o -o exec01
/usr/bin/ld: ex01.o: in function `main':
ex01.cpp:(.text+0x9): undefined reference to `show()'
/usr/bin/ld: ex01.cpp:(.text+0x13): undefined reference to `show2(int)'
/usr/bin/ld: ex01.cpp:(.text+0x29): undefined reference to `show3(int, double)'
/usr/bin/ld: ex01.cpp:(.text+0x44): undefined reference to `show4(int, int, double)'
collect2: error: ld returned 1 exit status

4.1 查看汇编结果

4.1.1 ex01.cpp 汇编结果
$ g++ -S -fverbose-asm -g ex01.cpp -o ex01.s

以下是 test.s 的部分汇编结果

main:
.LFB1522:
	.file 1 "ex01.cpp"
	.loc 1 8 1
	.cfi_startproc
	endbr64	
	pushq	%rbp	#
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp	#,
	.cfi_def_cfa_register 6
# ex01.cpp:9: 	show();
	.loc 1 9 6
	call	_Z4showv@PLT	#
# ex01.cpp:10: 	show2(1);
	.loc 1 10 7
	movl	$1, %edi	#,
	call	_Z5show2i@PLT	#
# ex01.cpp:11: 	show3(1, 2.0);
	.loc 1 11 7
	movq	.LC0(%rip), %rax	#, tmp84
	movq	%rax, %xmm0	# tmp84,
	movl	$1, %edi	#,
	call	_Z5show3id@PLT	#
# ex01.cpp:12: 	show4(1, 2, 3.0);
	.loc 1 12 7
	movq	.LC1(%rip), %rax	#, tmp85
	movq	%rax, %xmm0	# tmp85,
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	_Z5show4iid@PLT	#
# ex01.cpp:13: 	return 0;

可以看到 4 个 show 函数在 cpp 中汇编后, 分别对应 如下 的函数名

show()              _Z4showv@PLT

show2(int a)            _Z5show2i@PLT

show3(int a, double b)       _Z5show3id@PLT

show4(int a, int b, double c)   _Z5show4iid@PLT

@PLT不知道是什么…

4.1.2 test.c 汇编结果

为了方便查看,对 test.c 增加一个 main 函数, 看看调用 show 函数时,用的名字是什么

不加 main 函数, 直接看 函数 的汇编也一样

#include "test.h"
void show()
{
	printf("hello world CCCC\n");
}

void show2(int a)
{
	printf("hello world CCCC %d\n", a);
}


void show3(int a, double b)
{
	printf("hello world CCCC %f\n", a + b);
}

void show4(int a, int b, double c)
{
	printf("hello world CCCC %f\n", a + b + c);
}

int main()   // 增加 main 函数
{
	show();
	show2(1);
	show3(1, 2.0);
	show4(1, 2, 3.0);

	return 0;
}

得到 test.c 的汇编结果, 注意要用 gcc

$ gcc -S -fverbose-asm -g test.c -o test.s

截取的部分汇编结果

main:
.LFB4:
	.loc 1 26 1
	.cfi_startproc
	endbr64	
	pushq	%rbp	#
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp	#,
	.cfi_def_cfa_register 6
# test.c:27: 	show();
	.loc 1 27 2
	movl	$0, %eax	#,
	call	show	#
# test.c:28: 	show2(1);
	.loc 1 28 2
	movl	$1, %edi	#,
	call	show2	#
# test.c:29: 	show3(1, 2.0);
	.loc 1 29 2
	movq	.LC3(%rip), %rax	#, tmp84
	movq	%rax, %xmm0	# tmp84,
	movl	$1, %edi	#,
	call	show3	#
# test.c:30: 	show4(1, 2, 3.0);
	.loc 1 30 2
	movq	.LC4(%rip), %rax	#, tmp85
	movq	%rax, %xmm0	# tmp85,
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	show4	#
# test.c:32: 	return 0;

可以看到 4 个 show 函数在 C 语言中 汇编后, 分别对应 如下 的函数名

show()               show

show2(int a)           show2

show3(int a, double b)      show3

show4(int a, int b, double c)   show4

4.1.3 对比
函数名C 语言编译结果 (test.c)无extern “C”
C++ 编译结果 (ex01.cpp)
show()show_Z4showv@PLT
show(int a)show2_Z5show2i@PLT
show(int a, double b)show3_Z5show3id@PLT
show(int a, int b, double c)show4_Z5show4iid@PLT

所以,如果 ex01.cpp 去调用 test.c 中的 show 函数,在链接时就会由于 函数名称 对不上而报错,无法解析 show(void), show(int), show(int ,double), show(int, int, double)

"C++无法解析C函数"
函数 show 在C++文件中声明 (调用),在 C 文件中定义

C++ 文件中 show 函数名按照 函数+形参 处理,C 中直接只用原始函数名,两者不一致

4.1.4 在 ex01.cpp 中增加 extern “C” 后查看汇编结果

ex01.cpp 这里使用 3.1 中的 复合 方式

#include<iostream>
//#include "test.h"

using namespace std;

//C++ 中调用 C语言方法 使用 C语言方式链接 show() 方法
extern "C"
{
	void show();
	void show2(int a);
	void show3(int a, double b);
	void show4(int a, int b, double c);
}

int main()
{
	show();
	show2(1);
	show3(1, 2.0);
	show4(1, 2, 3.0);
	return 0;
}

重新得到 ex01.cpp 的汇编结果

$ g++ -S -fverbose-asm -g ex01.cpp -o ex01.s
main:
.LFB1522:
	.file 1 "ex01.cpp"
	.loc 1 15 1
	.cfi_startproc
	endbr64	
	pushq	%rbp	#
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp	#,
	.cfi_def_cfa_register 6
# ex01.cpp:16: 	show();
	.loc 1 16 6
	call	show@PLT	#
# ex01.cpp:17: 	show2(1);
	.loc 1 17 7
	movl	$1, %edi	#,
	call	show2@PLT	#
# ex01.cpp:18: 	show3(1, 2.0);
	.loc 1 18 7
	movq	.LC0(%rip), %rax	#, tmp84
	movq	%rax, %xmm0	# tmp84,
	movl	$1, %edi	#,
	call	show3@PLT	#
# ex01.cpp:19: 	show4(1, 2, 3.0);
	.loc 1 19 7
	movq	.LC1(%rip), %rax	#, tmp85
	movq	%rax, %xmm0	# tmp85,
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	show4@PLT	#
# ex01.cpp:20: 	return 0;
函数名C 语言编译结果 (test.c)extern "C"下
C++ 编译结果 (ex01.cpp)
show()showshow@PLT
show(int a)show2show2@PLT
show(int a, double b)show3show3@PLT
show(int a, int b, double c)show4show4@PLT

可以看到现在 test.cex01.cpp 汇编下 的 函数名已经一致了

这样就可以成功链接了(记得去掉4.1.2 在 test.c 中添加的 mian 函数)

4.1.5 在 test.h 中使用条件编译的方式增加 extern “C”

不用 4.1.4 的方式,现在 在 test.h 中添加 extern “C”

也就是使用如下的测试代码

test.h

#pragma once
#include<stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

void show();

void show2(int a);

void show3(int a, double b);

void show4(int a, int b, double c);

#ifdef __cplusplus
}
#endif

test.c

#include "test.h"

void show()
{
	printf("hello world CCCC\n");
}

void show2(int a)
{
	printf("hello world CCCC %d\n", a);
}


void show3(int a, double b)
{
	printf("hello world CCCC %f\n", a + b);
}

void show4(int a, int b, double c)
{
	printf("hello world CCCC %f\n", a + b + c);
}

// int main()
// {
// 	show();
// 	show2(1);
// 	show3(1, 2.0);
// 	show4(1, 2, 3.0);

// 	return 0;
// }

ex01.cpp

#include<iostream>
#include "test.h"

using namespace std;

int main()
{
	show();
	show2(1);
	show3(1, 2.0);
	show4(1, 2, 3.0);
	return 0;
}

首先查看 ex01.cpp 的汇编结果

$ g++ -S -fverbose-asm -g ex01.cpp -o ex01.s
main:
.LFB1522:
	.file 1 "ex01.cpp"
	.loc 1 8 1
	.cfi_startproc
	endbr64	
	pushq	%rbp	#
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp	#,
	.cfi_def_cfa_register 6
# ex01.cpp:9: 	show();
	.loc 1 9 6
	call	show@PLT	#
# ex01.cpp:10: 	show2(1);
	.loc 1 10 7
	movl	$1, %edi	#,
	call	show2@PLT	#
# ex01.cpp:11: 	show3(1, 2.0);
	.loc 1 11 7
	movq	.LC0(%rip), %rax	#, tmp84
	movq	%rax, %xmm0	# tmp84,
	movl	$1, %edi	#,
	call	show3@PLT	#
# ex01.cpp:12: 	show4(1, 2, 3.0);
	.loc 1 12 7
	movq	.LC1(%rip), %rax	#, tmp85
	movq	%rax, %xmm0	# tmp85,
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	show4@PLT	#
# ex01.cpp:13: 	return 0;

然后查看 test.c 的汇编结果 , 同样加上 main 函数

$ gcc -S -fverbose-asm -g test.c -o test.s
main:
.LFB4:
	.loc 1 26 1
	.cfi_startproc
	endbr64	
	pushq	%rbp	#
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp	#,
	.cfi_def_cfa_register 6
# test.c:27: 	show();
	.loc 1 27 2
	movl	$0, %eax	#,
	call	show	#
# test.c:28: 	show2(1);
	.loc 1 28 2
	movl	$1, %edi	#,
	call	show2	#
# test.c:29: 	show3(1, 2.0);
	.loc 1 29 2
	movq	.LC3(%rip), %rax	#, tmp84
	movq	%rax, %xmm0	# tmp84,
	movl	$1, %edi	#,
	call	show3	#
# test.c:30: 	show4(1, 2, 3.0);
	.loc 1 30 2
	movq	.LC4(%rip), %rax	#, tmp85
	movq	%rax, %xmm0	# tmp85,
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	show4	#
# test.c:32: 	return 0;

对比汇编结果

函数名C 语言编译结果 (test.c)条件编译 extern “C”
C++ 编译结果 (ex01.cpp)
show()showshow@PLT
show(int a)show2show2@PLT
show(int a, double b)show3show3@PLT
show(int a, int b, double c)show4show4@PLT

C++ 和 C 中的函数名保持一致

5. 小结

函数名C 语言编译结果 (test.c)无extern “C”
C++ 编译结果 (ex01.cpp)
show()show_Z4showv@PLT
show(int a)show2_Z5show2i@PLT
show(int a, double b)show3_Z5show3id@PLT
show(int a, int b, double c)show4_Z5show4iid@PLT

C 语言和 C++ 语言在函数命名上使用了不同的规则

  • C语言:不支持函数重载,编译后直接使用原始函数名

  • C++:支持函数重载,编译后使用 函数名 + 形参 重新定义了函数名字

extern “C” 告诉 C++ 编译器 使用 C语言 的方式 来对函数进行 编译

6. 函数重载

由此也可以对 C++ 函数重载的原理有一定的认识

C++ 使用 函数名 + 形参 重新定义了函数名字,所以相同名字的函数,只要有不同形参列表,编译后就能拥有不同的名字,就能加以区分

也可以看到编译后没有把 返回值类型 加入到函数名中,所以 不能 依靠 返回值类型 来实现函数重载。

在linux下 查看 C++ 函数重载的汇编结果

overload.cpp

#include<iostream>
using namespace std;

void func(int a)
{
    cout<<a<<endl;
}

void func(int a,int b)
{
    cout<<a+b<<endl;
}

void func(int a,int b,double c)
{
    cout<<a+b+c<<endl;
}

extern "C" void func(int a,int b,int c,double d){cout<<a+b+c+d<<endl;}

/*
extern "C" 的方式只能定义一次 func, 之后就重名了
linux gcc下报错     overload.cpp:20:17: error: conflicting declaration of C function ‘void func(int, int, int, int, double)’
windows vs2019下报错   “func”: 无法重载具有外部 "C" 链接的函数
*/

//extern "C" void func(int a,int b,int c,int d, double e){cout<<a+b+c+d+e<<endl;}  

int main()
{
    func(1);
    func(1,2);
    func(1,2,3.0);
    func(1,2,3,4.0);
    return 0;
}

获得汇编结果

$ g++ -S -fverbose-asm -g overload.cpp -o overload.s
main:
.LFB1526:
	.loc 1 22 5
	.cfi_startproc
	endbr64	
	pushq	%rbp	#
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp	#,
	.cfi_def_cfa_register 6
# overload.cpp:23:         func(1);
	.loc 1 23 13
	movl	$1, %edi	#,
	call	_Z4funci	#
# overload.cpp:24:         func(1,2);
	.loc 1 24 13
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	_Z4funcii	#
# overload.cpp:25:         func(1,2,3.0);
	.loc 1 25 13
	movq	.LC0(%rip), %rax	#, tmp84
	movq	%rax, %xmm0	# tmp84,
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	_Z4funciid	#
# overload.cpp:26:         func(1,2,3,4.0);
	.loc 1 26 13
	movq	.LC1(%rip), %rax	#, tmp85
	movq	%rax, %xmm0	# tmp85,
	movl	$3, %edx	#,
	movl	$2, %esi	#,
	movl	$1, %edi	#,
	call	func	#
# overload.cpp:27:         return 0;
函数名C ++编译结果 (overload.cpp)
func(int a)_Z4funci
func(int a, int b)_Z4funcii
func(int a, int b, double c)_Z4funciid
extern “C” func(int a, int b, int c, double)func

可以看到,前 3 个 func 使用了 函数名 + 形参 重新定义了名字,而 最后 1 个 func 使用了 extern “C”,告诉 C++ 编译器使用 C 语言的方式进行编译命名,且 使用 extern “C” 的方式只能定义一次 func, 因为之后就重名了,全都是func。

所以 C++ 可以函数重载,C 不行

从汇编的角度看 C++ 中 extern “C” 的作用 及 C++ 函数重载的实现的基本原理

7. 参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值