NDK开发之编译原理与语法

本文详细介绍了Android NDK开发中的编译流程,包括预处理、编译、汇编和链接四步,并讲解了静态库与动态库的区别及创建方法。此外,还探讨了Makefile的规则、shell语法以及CMake在Android Studio中的应用,为Android原生开发提供了深入理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.编译流程

编译就是将高级语言的代码转化成可执行的二进制目标代码。主要包含四个步骤:预处理、编译、汇编、链接。

预处理:就是对各种预处理命令的处理,主要是宏替换、文件引入、去除空行和注释等。linux系统中预处理命令:gcc -E test.c -o test.i。-E表示gcc在预处理后停止编译、汇编和链接操作。

编译:将预处理后的代码翻译成汇编语言。会先分析后综合,分析就是进行词法、语法、语义分析和中间代码的生成,综合就是代码优化和代码生成。编译命令gcc -c test.i -o test.s。

 汇编:把编译生成的汇编代码.s文件转化成为可执行的二进制的目标文件。gcc -c test.s -o test.o。

链接:把多个的目标代码和所需的库文件链接生成目标代码。gcc  test.o -o test。-o是一个重命名选项,不使用-o选项时默认的是生成a.out,使用后可以生成可执行文件。

执行文件:./test。删除目标文件:rm -rf test。生成可执行文件:gcc -o test test.c。查看文件详情ls -lh。

2.静态库和动态库

静态库是目标文件(.o)的集合,一般以.a结尾,只存在生成可执行文件阶段。链接器将从库文件中取得所需代码复制到生成的可执行代码中,可执行代码中包含库代码的完整拷贝。多次使用就有多份拷贝,包会比较大;静态库有更新,所有使用它的程序都需要重新编译。命令将目标文件打包生成静态库:ar rcs libtest.a test.o。查看静态库内容:ar t libtest.a。  

动态库在链接阶段没有复制到目标代码中,在运行阶段才由系统动态到内存中。系统只要加载一次动态库,系统中不同的程序都可以获得内存中相同动态库的副本,可以节省大量内存空间。生成动态库:gcc -shared -fPIC -o libtest.so test.o。

编译可执行文件并链接库文件tool:gcc -o main main.c -L. -ltool。-L.表示编译程序按照指定目录去找,.按照当前目录去找。 -l指定要链接的库。ldd main 可以查看可执行文件依赖的库。当目录中动态库和静态库的名字相同时会优先加载动态库,链接动态库需要先设置环境变量,指定查找共享库,在默认目录下找不到该库时使用:LD_LIBRARY_PATH=. ./main。

一、makefile语法详解

Makefile定义了一系列规则的去指定哪些文件先编译、是否需要重新编译,如何链接等,是自动化编译的脚本,告诉make如何编译和链接,生成目标文件。

Makefile规则:taget : prerequisites1 prerequisites2 command (command 和target在一行分号隔开;另起一行必须加tab;命令太长使用\换行)。命令.PHONY:clean     clean:    -rm main,*.o显示指定clean是伪目标,不会生成clean文件。-表示如果文件执行不成功,则跳过继续执行。

makefile工作原理:输入make命令后,make会首先在当前目录找makefile(Makefile)文件,在Makefile中找到第一个target并作为最终目标。如果target已经存在并且是最新的则执行文件main。target不存在或者不是最新的,则需要判断当前的依赖文件是否是最新的,让最新的依赖文件根据依赖规则执行依赖命令重新生成最新的target。最后完成make的终极目标执行文件main。如果在查找依赖关系过程中,找不到被依赖文件,则make报错退出。

Makefile中的变量:工程需要加入新的.o文件,可能会忘记修改某处.o文件,最好使用变量统一修改。定义object=main.o tool.o,使用$(object);。在makefile 中使用include类引入其他makefile文件eg :-include foo.make *.mk,-表示找不到文件也继续执行。

 函数:带参数和不带参数。不带参数:define FUNC $( info echo "hello") endef  ,调用$(call FUNC)。带参数:define FUNC1 $( info echo $(1)$(2)) endef  ,调用$(call FUNC,Hello,World)。

makefile工作流程:读入所有makefile,读入被include的其他makefile文件,初始化所有的变量,推导隐晦规则并分析所有规则,为所有的目标文件创建依赖关系链;根据依赖关系,确定哪些目标文件需要重新生成;执行生成命令。

二、shell语法详解

shell运行在linux系统中,构建大型项目需要不断的编译、打包等,shell是构建项目命令的集合,能避免大量重复的操作,可以编译大部分的第三方库并合并到apk中。bin/bash是shell语法开发,在linux中使用的命令都对应一个shell文件。shell文件是以#!bin/bash开头,后缀是.sh的可执行文件,运行shell文件的指令是'chmod 777 a.sh ;./a.sh','/bin/bash a.sh','sh a.sh'。

1.变量

分为环境变量、局部变量、系统变量。shell编程语言是弱类型,赋值的时候就定义了。定义:a=10(变量左右都不能有空格),引用:$a。$PWD输出当前路径,$0输出当前项目名称,$1输出第一个参数,$*是输出所有的参数,$#是所有参数的个数,$?是返回前一条指令的返回结果。

2.循环

for格式:'for((i=0;i<$#;i++))do   ..done','for i in `seq 1 15` do ... done',。eg 累加:j=0 for((i=0;i<100;i++))do j=`expr $i + $j ` done echo $j。压缩所有sh文件: for i in `find /root -name "*.sh"` do   tar -czf *.tgz  $i done。

while格式:‘while(条件)do .. done’。((i<100))算数逻辑,命令逻辑用[[ $i -lt 100 ]]。eg 读文件while read line do echo $line done</root/test.txt

3.条件

if格式:if(); fi。if() ;then ..else..fi。 没有{},语句前必须加tab键,不能是空格,空格在shell中表示分割,不能随便加。shell是非面向对象语言,很多操作是可以通过运算符实现,不仅可以使用算数比较,可以使用命令,-f文件是否存在,-d目录是否存在。eg:b=/root/a if [ !-d $b ] then  mkdir -p $b

4.运算符

算数运算符:+、-、*、/、% expr $a * $b;=、==、!=做条件表达式,中括号前后必须有空格。算数运算方式:(())、[]、let、expr外部程式。

关系运算符-eg是整形比较相等,-ne不等于,-ge是大于等于,-le是小于等于,-gt大于,-lt小于。

布尔运算符!,-a单方成立,-o双方成立。字符串运算符有=、!=、-z、-n、$a(是否为空)。

5.重定向

输出text.txt的内容到屏幕:cat 0<text.txt     。将hello输入到test中:echo hello >test.txt。

6.函数

必须先定义,再使用。格式:定义函数fun(){} function fun(){},调用fun 10,函数中可以使用成员变量$a,也可以使用参数,shell语法没有形参,使用$1、$2代替参数。有返回值的函数分为两种return或者echo返回。return方式 最后使用$?返回最后一条的执行结果,结算结果必须小于255,eg:函数中最后一行添加代码return $[$a*2] ,调用该函数后输出$?即可获得值 $[$a*2]。echo方式则不同,在函数最后一行添加代码 echo $[$a*2] ,调用该函数并将返回结果赋值给变量r=`fun()`,调用后输出$r即可。

三、CMake详解

android  studio 2.2以上版本默认的构建工具就是cmake。cmake是make的高级版,是一个跨平台的编译工具,能够通过简单的指令描述不同平台、不同编译器上程序的编译过程,产生平台对应的makefile和project文件,但并不直接产生最终软件,由其他软件根据脚本构建。Android studio根据cmake产生的构建系统是ninja,我们不需要关心ninja脚本,知道怎么配置cmake就可以了。

cmake源文件一般命名为CMakeLists.txt或者以.cmake为拓展名。源文件包含命令、注释、空格和换行,命令是指所有能执行的语句,可以是系统内置函数、自定义函数和宏定义等。可以通过add_subdirectory()添加子目录的cmake文件。

1.cmake的注释

单行注释以#开头,多行注释以#[[ ]]包裹。

2.cmake的语法

所有变量都是string类型,大小写敏感。变量定义 set(a 1),set(list 1 2 3),set(list "1;2;3"),调用message("输出a:${a}"),unset()来移除变量。作用域有全局层、目录层、函数层。函数层定义的变量会覆盖目录层,目录层会覆盖全局层(cache变量),也就是系统在查找变量的时候首先是从函数层开始查找的,修改上层函数的作用域并不会真正修改上层函数的作用域,只是获取上层函数的副本而已。

操作符优先级:括号>一元(COMMAND DEFINE EXIST)>二元(比较运算符、STR、VERSION)>逻辑运算符(AND OR NOT)

条件语句:if()...else if()...else()...endif()

循环语句:while(NOT a STREQUAL "xxx")....endwhile()

循环遍历:foreach(item 1 2 3)....endforeach(item),foreach(item RANGE 3),foreach(item RANGE 1 9 3),foreach(item IN LISTS list_v)。

自定义函数: function(func a b)...endfunction(func),调用:func 1 2。$(ARGC)表示输入的有多少个参数,$(ARGV)返回所有的输入参数,$(ARGV0)返回第一个函数。

宏定义:macro(m a b)...endmacro(m)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值