1. 程式人生 > >C與C++混編

C與C++混編

clu 報錯 查看 工具 混編 define 別了 判斷 需要

了解一下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++混編