C與C++混編
了解一下C與C++如何合作,gcc和g++編譯出來的東西有什麽區別。
工具使用
objdump是個好工具,可以用於查看.o
文件的內容,也可以查看可執行文件的內容。
查看符號表
objdump -t foo.o
查看正文段
objdump -S foo.o
查看所有session
objdump -D foo.o
正文
先來看下面這個文件foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
printf("foo\n");
}
以gcc -c foo.c
編譯結果如下
0000000000000000 <_Z3foov>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: bf 00 00 00 00 mov $0x0,%edi 9: e8 00 00 00 00 callq e <_Z3foov+0xe> e: 90 nop f: 5d pop %rbp 10: c3 retq
以g++ -c foo.c
編譯結果如下
0000000000000000 <foo>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: bf 00 00 00 00 mov $0x0,%edi 9: e8 00 00 00 00 callq e <foo+0xe> e: 90 nop f: 5d pop %rbp 10: c3 retq
這個文件足夠簡單,可以看到區別就只是函數名而已,gcc並沒有改變函數名,而g++在前後加了一些串。其實g++將參數信息插在函數名的尾部了,如上的_Z3foov
中的v
就代表了void。
- 如果是有1個參數int,那函數名是
_Z3fooi
。 - 如果是有1個參數double,那函數名是
_Z3food
。 - 如果有兩個參數int和double,那函數名應該是
_Z3fooid
。
如果參數是個自定義的類呢,比如:
int foo(My my)
{
return 0;
}
被編譯成
0000000000000047 <_Z3foo2My>: 47: 55 push %rbp 48: 48 89 e5 mov %rsp,%rbp 4b: 89 7d f0 mov %edi,-0x10(%rbp) 4e: b8 00 00 00 00 mov $0x0,%eax 53: 5d pop %rbp 54: c3 retq
可以看到,直接以類名拼接在末尾。
如果是個std的類呢?比如string
void foo(std::string my)
{
printf("foo%s\n", my.c_str());
}
被編譯成
000000000000001a <_Z3fooNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE>:
1a: 55 push %rbp
1b: 48 89 e5 mov %rsp,%rbp
1e: 48 83 ec 10 sub $0x10,%rsp
22: 48 89 7d f8 mov %rdi,-0x8(%rbp)
26: 48 8b 45 f8 mov -0x8(%rbp),%rax
2a: 48 89 c7 mov %rax,%rdi
2d: e8 00 00 00 00 callq 32 <_Z3fooNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x18>
32: 48 89 c6 mov %rax,%rsi
35: bf 00 00 00 00 mov $0x0,%edi
3a: b8 00 00 00 00 mov $0x0,%eax
3f: e8 00 00 00 00 callq 44 <_Z3fooNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x2a>
44: 90 nop
45: c9 leaveq
46: c3 retq
很長很長,因為類名確實很長,這個你用lstrace跑個程序就知道了,很多函數名都很長得看不懂。
C++調用C
在C++源文件中是不能直接調用C源文件中的函數的,鏈接的時候就會報對‘foo()’未定義的引用
,因為C++源文件編譯時沒問題,鏈接時就找不到符號了.舉個例子,現在有文件main.cpp、foo.h、foo.c。
main.cpp內容如下:
#include "foo.h"
int main()
{
foo();
return 0;
}
foo.h內容如下:
#ifndef __FOO__
#define __FOO__
void foo();
#endif
foo.c內容如下:
#include <stdio.h>
void foo()
{
printf("foo\n");
}
現在以如下命令編譯他們
g++ -c main.cpp
gcc -c foo.c
g++ -o test foo.o main.o # 這一步會報錯
報錯內容:
main.c:(.text+0x10):對‘foo()’未定義的引用
collect2: error: ld returned 1 exit status
這是因為在鏈接兩個.o
文件時,找不到foo
這個函數才報的錯。foo
確實是在foo.o
裏邊的,只不過main.o
中其實需要的是函數_Z3foov
才對。
正確的做法是修改foo.h
文件如下
#ifndef __FOO__
#define __FOO__
extern "C" {
void foo();
}
#endif
這樣編譯出來的foo.o
沒有任何區別,但是main.o
就有區別了,裏面的符號_Z3foov
全被替換成foo
了(用objdump -t查看),這樣鏈接起來就沒問題。
看到這裏,extern "C"
的用法也就清晰了,即告訴g++編譯器,大括號內的符號都以C的符號命名方式去調用。值得註意的是,通常foo.h
不是一直被cpp文件所include的,有時一個程序會有C和CPP文件同時需要include它,一般需要在使用extern "C"
的時候用宏__cplusplus
來判斷此時的編譯器是不是C++的,就像下面這樣:
#ifndef __FOO__
#define __FOO__
#ifdef __cplusplus
extern "C" {
#endif
void foo();
#ifdef __cplusplus
}
#endif
#endif
C與C++混編