C與C++混編

瞭解一下C與C++如何合做,gcc和g++編譯出來的東西有什麼區別。session

工具使用

objdump是個好工具,能夠用於查看.o文件的內容,也能夠查看可執行文件的內容。函數

查看符號表
objdump -t foo.o工具

查看正文段
objdump -S foo.ocode

查看全部session
objdump -D foo.o編譯器

正文

先來看下面這個文件foo.cstring

#include <stdio.h>
#include "foo.h"

void foo()
{
    printf("foo\n");
}

gcc -c foo.c編譯結果以下it

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編譯結果以下io

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

若是參數是個自定義的類呢,好比:test

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
相關文章
相關標籤/搜索