C与C++混编
介绍
了解一下C与C++如何合作,gcc和g++编译出来的东西有什么区别。C++为了支持重载等特性,编译出来的符号和C是不一样的。
每个公司都会有一些古老的库,几乎每个程序都在使用它,它可能是C写的,或者是C++写的,通常情况下,我们能做的就是调用里面的函数,而不能修改这个库,因为很多程序都在用它,你改了它就得测所有的程序,得不偿失。
工具使用
objdump是个好工具,可以用于查看.o
文件的内容,也可以查看可执行文件的内容。主要是可以用来查看gcc
和g++
编译出来的符号有什么区别。
查看符号表
objdump -t foo.o
查看正文段
objdump -S foo.o
查看所有session
objdump -D foo.o
符号入门
先来看下面这个文件foo.c
#include <stdio.h>
void foo()
{
printf("foo\n");
}
以g++ -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
以gcc -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
这个文件足够简单,可以看到区别就只是函数名而已,这里我们只关注第9行。可以看出,gcc并没有改变函数名foo
,而g++在前后加了一些串变成了_Z3foov
。其实g++将参数信息插在函数名的尾部了,_Z3foov
中的后缀v
就代表了void(PS: 我不知道_Z3
表示啥)。稍微改一下函数的参数,用g++编译一下看看:
- 如果
foo
是有1个参数int,那函数名是_Z3fooi
。 - 如果
foo
是有1个参数double,那函数名是_Z3food
。 - 如果
foo
有两个参数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
可以看到,直接以类名拼接在末尾(PS: 我不知道2
表示啥)。
如果是个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++源文件编译时没问题,链接时就找不到符号了,有了上面的符号基础,我们可以知道这是因为gcc和g++编译出来的符号不一致导致的。举个例子,现在有文件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
查看),这样链接起来就没问题。当然了,在一些古老的库中,头文件那么多,难道要一个个改吗?况且改完头文件还得重新用gcc编译。这是没必要的,此时只需要再新建一个头文件,比如foo_cpp.h
,把需要用到的C头文件写在大括号里面就可以了,比如:
#ifndef __FOO_CPP__
#define __FOO_CPP__
#ifdef __cplusplus // 很重要
extern "C" {
#endif
#include "foo.h"
#ifdef __cplusplus
}
#endif
#endif
看到这里,extern "C"
的最常见的用法也就清晰了,件即告诉g++编译器,大括号内的符号都以C的符号命名方式去调用。值得注意的是,一般需要在使用extern "C"
的时候用宏__cplusplus
来判断此时的编译器是不是C++的,这样的做法百利而无害。
但是,这样就改到了旧的C模块了,这通常不是我们想要的,这里介绍一个更简单的方法。直接改main.cpp
,用extern "C"
括起需要用到的C头文件,如下
extern "C" {
#include "foo.h"
}
int main()
{
foo();
return 0;
}
这种方式更加方便快捷,还不用动旧代码,所以用得比较多。
C调用C++
C调用C++稍微有些不同,因为C没有extern "C"
这个东西,所以还得从C++的代码入手,使得它可以被C代码所调用。
方法1:
如果原来的C++代码可以改的话,直接在头文件里面套上extern "C"
全部括起来,然后C代码中想用哪个函数就直接将函数声明拷过来,前面加上extern
(告诉gcc这个函数是在其他模块定义的)。
apple.cpp内容如下
#include "apple.h"
Apple::Apple() : m_nColor(0)
{
}
void Apple::SetColor(int color)
{
m_nColor = color;
}
int Apple::GetColor(void)
{
return m_nColor;
}
int set_and_get_color(int n)
{
Apple a;
a.SetColor(n);
return a.GetColor();
}
apple.h内容如下
#ifndef __APPLE_H__
#define __APPLE_H__
#ifdef __cplusplus // 新加上的
extern "C" {
#endif
class Apple
{
public:
Apple();
int GetColor(void);
void SetColor(int color);
private:
int m_nColor;
};
int set_and_get_color(int);
#ifdef __cplusplus // 新加上的
}
#endif
#endif
main.c内容如下
#include <stdio.h>
extern int set_and_get_color(int); // 函数如果太多可以弄个头文件单独写
int main(void)
{
printf("color: %d\n", set_and_get_color(5666));
return 0;
}
按照如下命令编译它们
g++ -c apple.cpp
gcc -c main.c
gcc -o test main.o apple.o -lstdc++ # 链接
注意到,旧的C++的代码仍然是用g++编译,新的C代码是用gcc编译,最后再把它们链接起来就是可执行程序。这个方法的缺点就是需要改到旧的C++头文件,导致C++代码重新编译,问题也不是很大,可以考虑。
方法2:
如果旧的C++代码太古董了,一点都不想改的话,可以专门写个wrapper模块,单独给main.cpp
用。这个wrapper模块是C++代码(g++编译)。
apple.cpp和上面方法1是一样的。
apple.h内容如下
#ifndef __APPLE_H__
#define __APPLE_H__
class Apple
{
public:
Apple();
int GetColor(void);
void SetColor(int color);
private:
int m_nColor;
};
int set_and_get_color(int);
#endif
wrapper.cpp的内容如下
#include "wrapper.h"
#include "apple.h" // 这行不能放头文件,编不过的
int c_set_and_get_color(int x)
{
return set_and_get_color(x);
}
wrapper.h的内容如下
#ifdef __cplusplus // 这个宏判断必不可少
extern "C" {
#endif
int c_set_and_get_color(int);
#ifdef __cplusplus
}
#endif
main.c的内容如下
#include <stdio.h>
#include "wrapper.h"
int main(void)
{
printf("color: %d\n", c_set_and_get_color(5666));
return 0;
}
按照如下命令编译它们
g++ -c apple.cpp
g++ -c wrapper.cpp
gcc -c main.c
gcc -o test main.o apple.o wrapper.o -lstdc++ # 链接
注意到,wrapper.o
是用g++编的,main.o
是用gcc编的,而且wrapper.h
在wrapper.o
和main.o
中都有用到,所以__cplusplus
宏判断是必不可少的,因为对于wrapper.o
来说,extern "C"
是需要的,对于main.o
来说又是不需要的。
总结
C++调C很简单,在需要调用C接口时用extern "C"
将头文件括起来就行了,不需要改原来的代码(当然了,你想改也没问题)。
C调C++较麻烦,有两种方法:
- 改旧的C++头文件,插入
extern "C"
后,C代码可直接用C++接口(以extern声明一下函数即可)。 - 用C++写个wrapper模块,提供一些C接口,里面再去调用C++接口。
C与C++混编的更多相关文章
- Swift和Objective-C混编注意事项
前言 Swift已推出数年,与Objective-C相比Swift的语言机制及使用简易程度上更接地气,大大降低了iOS入门门槛.当然这对新入行的童鞋没来讲,的确算是福音,但对于整个iOS编程从业者来讲 ...
- iOS 里面 Swift与Objective-C混编,Swift与C++混编的一些比较
即使你尽量用Swift编写iOS程序,难免会遇到部分算法是用C++语言编写的.那你只能去问问”度娘“或“狗哥”怎么用Swift调用C++算法. 一,C,C++, Objective-C,S ...
- IOS --- OC与Swift混编
swift 语言出来后,可能新的项目直接使用swift来开发,但可能在过程中会遇到一些情况,某些已用OC写好的类或封装好的模块,不想再在swift 中再写一次,哪就使用混编.这个在IOS8中是允许的. ...
- Processing与Java混编初探
Processing其实是由Java开发出的轻量级JAVA开发语言,主要用于做原型,有setup.draw两个主接口和几个消息相应接口 Processing和Java混编很简单...在Java中引入外 ...
- obeject-c 与 swift 混编
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 ...
- iOS开发--Swift 如何完成工程中Swift和OC的混编桥接(Cocoapods同样适用)
由于SDK现在大部分都是OC版本, 所以假如你是一名主要以Swift语言进行开发的开发者, 就要面临如何让OC和Swift兼容在一个工程中, 如果你没有进行过这样的操作, 会感觉异常的茫然, 不用担心 ...
- iOS之 C++与oc混编
声明:本文只是随笔,自己做个笔记方便以后查阅如要转载,注明出处.谢谢! 2016年第一篇随笔!!! 由于最近要搞一个项目用到c++的一些api所以要混编,于是就记录下这个过程中的一些细节上的东西! O ...
- swift混编oc碰到的问题
在swift中混编苹果官方的Reachability OC文件. 因为swift工程的target是生成framework而非app,framework中调用oc与app中使用桥接文件还不一样,参考: ...
- swift objective-及c语言 混编
在xocde6出来我们大部分代码都是用objective-c写的(部分C/C++),现在出生来了一个新的语言叫swift,那么如何既能使用我们之前的代码,还可以使用新语言呢, 本文就此做一下说明. 关 ...
- [ios][swift]swift混编
http://blog.csdn.net/iflychenyang/article/details/8876542(如何在Objective-C的头文件引用C++的头文件) 1.将.m文件扩展名改为. ...
随机推荐
- 使用c#特性,给方法或类打自定义标签再反射获取
给方法打自定义标签再反射获取 1.自定义特性类 using System; using System.Collections; using System.Collections.Generic; // ...
- java中的各种命令参数
java中有很多命令参数,这些命令参数有些是控制jvm行为的,有的则是供应用程序使用.我所了解的参数主要有三种,现在说一说这三种类型的参数. (1)命令行参数. 命令行参数就是类似与c语言的命令行参数 ...
- 九度oj 题目1009:二叉搜索树
题目1009:二叉搜索树 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:5733 解决:2538 题目描述: 判断两序列是否为同一二叉搜索树序列 输入: 开始一个数n,(1<=n&l ...
- Golang教程:循环语句
循环语句用于重复执行一段代码. for 语句是 Go 中唯一的循环语句.Go 没有提供其他语言(如 C)中的 while 和 do while 语句. for 语句语法 for 语句的语法如下: fo ...
- 次讲解js中的回收机制是怎么一回事。
在前几天的一篇闭包文章中我们简单的介绍了一下闭包,但是并没有深入的讲解,因为闭包涉及的知识点比较多,为了能够更好的理解闭包,今天讲解一下关于js中的回收机制. 在初识闭包一文中我说过js中有回收机制这 ...
- 移除TFS服务器关系
移除TFS服务器关系:1.将项目和从以前的TFS服务器断开.2.退出VS.3.找到C:\Documents and Settings\Administrator\Local Settings\Appl ...
- 定时器setTimeout()和setInterval()使用心得整理
JavaScript提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成. 一.setTimeout() setTimeout函 ...
- 三:SSM框架整合思路
一:jar包 1.spring(包括springmvc) 2.mybatis 3.mybatis-spring整合包 4.数据库驱动 5.第三方连接池 6.json依赖包jackson 二:整合思路 ...
- Linux+Git命令
Linux 文件与目录 cd命令: $ cd [path] //path为路径名称,这只是常规语法 1 详细用法如下: $ cd /d //进入d盘 $ cd d: //进入d盘 $ cd D: // ...
- C10K问题摘要
本文的内容是下面几篇文章阅读后的内容摘要: http://www.kegel.com/c10k.html (英文版) http://www.oschina.net/translate/c10k (中文 ...