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
相关文章
相关标签/搜索