linux交叉编译c⽂件,写给安卓程序员的CC++编译⼊门(交叉
编译,Makefile)
最近⼀直在和Linux C开发打交道,开发过程中会⽤到交叉编译和Makefile相关知识,但是对这块真的是没有了解,所以在⽹上搜索,到⼀篇不错的博客。本⽂⼤部分摘抄⾃该博客写给安卓程序员的C/C++编译⼊门(作者:嘉伟咯)。如有侵权请联系删除。
为什么要学C/C++编译
很多的安卓程序员可能都会⽤Android Studio写⼀些简单的C/C++代码,然后通过jni去调⽤,但是对C/C++是如何编译的其实并没有什么概念.有⼈可能会问,为什么安卓程序员会需要了解C/C++是如何编译的呢?
我⼀直都认为,要成为⼀个真正的⾼级安卓应⽤开发⼯程师,安卓源码和C/C++是两座绕不过的⼤⼭.安卓源码⾃然不必多说,⽽C/C++流⾏了⼏⼗年,存在着许多优秀的开源项⽬,我们在处理⼀些特定的需求的时候,可能会需要使⽤到它们.如脚本语⾔Lua,计算机视觉库OpenCV,⾳视频编解码库ffmpeg,⾕歌的gRPC,国产游戏引擎有些库提供了完整的安卓接⼝,有些提供了部分安卓接⼝,有些则没有.在做⼀些⾼级功能时,我们常常需要使⽤源码,通过裁剪和交叉编译,才能编译出可以在安卓上使⽤的so库.总之,安卓做深做精总避不开C/C++交叉编译。
C/C++编译器
类似java编译器javac可以将java代码编译成class⽂件,C/C++也有gcc、g++、clang等多种编译器可以⽤于编译C/C++代码.这⾥我们⽤gcc来举例。
gcc原名为GNU C 语⾔编译器(GNU C Compiler),因为它原本只能处理C语⾔.但GCC很快地扩展,变得可处理C++。后来⼜扩展能够⽀持更多编程语⾔,如Fortran、Pascal、Objective-C、Java、Ada、Go以及各类处理器架构上的汇编语⾔等,所以改名GNU编译器套件(GNU Compiler Collection)。
使⽤gcc其实只需要⼀个命令就能将⼀个c⽂件编译成可运⾏程序了:
gcc test.c -o test
通过上⾯这条命令可以将test.c编译成可运⾏程序test.但是其实C/C++的编译是经过了好⼏个步骤的,我这边先给⼤家⼤概的讲⼀讲。
C/C++的编译流程
C/C++的编译可以分为下⾯⼏个步骤:
预处理
相信学过C/C++的同学都知道"宏"这个东西,它在编译的时候会被展开替换成实际的代码,这个展开的步骤就是在预处理的时候进⾏的.当然,预处理并不仅仅只是做宏的展开,它还做了类似头⽂件插⼊、删除注释等操作.
预处理之后的产品依然还是C/C++代码,它在代码的逻辑上和输⼊的C/C++源代码是完全⼀样的.
我们来举⼀个简单的例⼦,写⼀个test.h⽂件和⼀个test.c⽂件:
//test.h
#ifndef TEST_H
#define TEST_H
#define A 1
#define B 2
/**
* add ⽅法的声明
*/
int add(int a, int b);
#endif
//test.c
#include "test.h"
/**
* add ⽅法定义
*/
int add(int a, int b) {
return a + b;
}
int main(int argc,char* argv[]) {
add(A, B);
return 0;
}
然后可以通过下⾯这个gcc命令预处理test.c⽂件,并且把预处理结果写到test.i:
gcc -E test.c -o test.i
然后就能看到预处理之后的test.c到底长什么样⼦了:
可以看到这⾥它把test.h的内容(add⽅法的声明)插⼊到了test.c的代码中,然后将A、B两个宏展开成了1和2,将注释去掉了,还在头部加上了⼀些信息.但是光看代码逻辑,和之前我们写的代码是完全⼀样的.
汇编代码
可能⼤家都听过汇编语⾔这个东西,但是年轻⼀点的同学不⼀定真正见过.简单来说汇编语⾔是将机器语⾔符号化了的语⾔,是机器不能直接识别的低级语⾔.我们可以通过下⾯的命令,将预处理后的代码编译成汇编语⾔:
gcc -S test.i -o test.s
然后就能看到⽣成的test.s⽂件了,⾥⾯就是我们写的c语⾔代码翻译⽽成的汇编代码:
.file "test.c"
.text
.globl add
.type add, @function
add:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %edx movl -8(%rbp), %eax addl %edx, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size add, .-add
.globl main
.type main, @function main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp
.cfi_def_cfa_register 6 subq $16, %rsp
movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $2, %esi
movl $1, %edi
call add
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
汇编
汇编这⼀步是将汇编代码编译成机器语⾔:
gcc -c test.s -o test.o
⽣成的test.o⽂件⾥⾯就是机器代码了,我们可以通过nm命令来列出test.o⾥⾯的符号:
nm test.o
得到的结果如下:
0000000000000000 T add
0000000000000014 T main
链接
由于我们的例⼦代码⽐较简单只有⼀个test.h和test.h,所以只⽣成了⼀个.o⽂件,其实⼀般的程序都是由多个模块组合成的.链接这⼀步就是将多个模块的代码组合成⼀个可执⾏程序.我们可以⽤gcc命令将多个.o⽂件或者静态库、动态库链接成⼀个可执⾏⽂件:
gcc test.o -o test
得到的就是可执⾏⽂件test了,可以直接⽤下⾯命令运⾏
./test
当然是没有任何输出的,因为我们就没有做任何的打印
编译so库
在安卓中我们⼀般不会直接使⽤C/C++编译出来的可运⾏⽂件.⽤的更多的应该是so库.那要如何编译so库呢?
⾸先我们需要将test.c中的main函数去掉,因为so库中是不会带有main函数的:
#include "test.h"
/**
* add ⽅法定义
*/
int add(int a, int b){
return a + b;
}
然后可以使⽤下⾯命令将test.c编译成test.so:
gcc -shared test.c -o test.so
其实也就是多了个-shared参数,指定编译的结果为动态链接库.
这⾥是直接将.c⽂件编译成so,当然也能像之前的例⼦⼀样先编译出.o⽂件再通过链接⽣成so⽂件.
当然⼀般编译动态链接库,我们还会带上-fPIC参数.
fPIC (Position-Independent Code)告诉编译器产⽣与位置⽆关代码,即产⽣的代码中没有绝对地址,全部使⽤相对地址.故⽽代码可以被加载器加载到内存的任意位置,都可以正确的执⾏.不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.因为它⾥⾯的代码并不是位置⽆关代码.如果被多个应⽤程序共同使⽤,那么它们必须每个程序维护⼀份.so的代码副本了.因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享.
交叉编译
通过上⾯的例⼦,我们知道了⼀个C/C++程序是怎么从源代码⼀步步编译成可运⾏程序或者so库的.但是我们编译出来的程序或者so库只能在相同系统的电脑上使⽤.
例如我使⽤的电脑是Linux系统的,那它编译出来的程序也就只能在Linux上运⾏,不能在安卓或者Windows上运⾏.
当然正常情况下不会有⼈专门去到android系统下编译出程序来给安卓去⽤.⼀般我们都是在PC上编译出安卓可⽤的程序,在给到安卓去跑的.这种是在⼀个平台上⽣成另⼀个平台上的可执⾏代码的编译⽅式就叫做交叉编译.
交叉编译有是三个⽐较重要的概念要先说明⼀下:
build : 当前你使⽤的计算机
host : 你的⽬的是编译出来的程序可以在host上运⾏
target : 普通程序没有这个概念。对于想编译出编译器的⼈来说此属性决定了新编译器编译出的程序可以运⾏在哪
linux下gcc编译的四个步骤如果我们想要交叉编译出安卓可运⾏的程序或者库的话就不能直接使⽤gcc去编译了.⽽需要使⽤Android NDK提供了的⼀套交叉编译⼯具链.
我们⾸先要下载Android NDK,然后配置好环境变量NDK_ROOT指向NDK的根⽬录.
然后可以通过下⾯命令安装交叉编译⼯具链:
$NDK_ROOT/build/tools/make-standalone-toolchain.sh \
--platform=android-19 \
--install-dir=$HOME/Android/standalone-toolchains/android-toolchain-arm \
--toolchain=arm-linux-androideabi-4.9 \
--stl=gnustl
然后我们就能在HOME/Android/⽬录下看到安装好的⼯具链了.进到HOME/Android/standalone-toolchains/android-toolchain-
arm/bin/⽬录下我们可以看到有arm-linux-androideabi-gcc这个程序.
它就是gcc的安卓交叉编译版本.我们将之前使⽤gcc去编译的例⼦全部换成使⽤它去编译就能编译出运⾏在安卓上的程序了:
如下⾯命令⽣成的so库就能在安卓上通过jni调⽤了:
$HOME/Android/standalone-toolchains/android-toolchain-arm/bin/arm-linux-androideabi-gcc -shared -fPIC test.c -o test.so
不同CPU架构的编译⽅式
当然安卓也有很多不同的CPU架构,不同CPU架构的程序也是不⼀定兼容的,相信⼤家之前在使⽤Android Studio去编译so的时候也有看到编译出来的库有很多个版本像armeabi、armeabi-v7a、mips、x86等.
那这些不同CPU架构的程序⼜要如何编译了.
我们可以在$NDK_ROOT/toolchains⽬录下看到者⼏个⽬录:
arm-linux-androideabi-4.9
aarch64-linux-android-4.9
mipsel-linux-android-4.9
mips64el-linux-android-4.9
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论