Android NDK编译选项设置全解

   在Android NDK开发中,有两个重要的文件:Android.mk和Application.mk,各尽其责,指导编译器如何编译程序,并决定编译结果是什么。本文将详细说明几个常见的NDK选项的配置,帮助大家理解相应的配置选项。

    一、Application.mk


       Application.mk实际上是轻量级Makefile,通常在$PROJECT/jni目录下,用于配置所有modules的编译变量,例子如下:

APP_ABI := armeabi arm64-v8a x86_64 x86 armeabi-v7a
NDK_TOOLCHAIN_VERSION := clang3.5
APP_STL := stlport_static
APP_OPTIM:= debuge


      1、APP_ABI(目标平台ABI类型)

       NDK编译中,APP_ABI默认选择armeabi ABI,可通过设置APP_ABI设置一个或者多个ABI,表一为不同的APP_ABI所对应的指令集。  

Instrunction set Value
ARMv5TE based CPU APP_ABI := armeabi
ARMv7 based CPU APP_ABI := armeabi-v7a
ARMv8 AArch64 APP_ABI := arm64-v8a
IA-32 APP_ABI := x86
Intel64 APP_ABI := x86_64
MIPS32 APP_ABI := mips
MIPS64(r6) APP_ABI := mips64
All supported instruction sets APP_ABI := all

表一:ABI类型

       在开发时可根据需求选择APP_ABI,对于ABI的选择需要考虑到效率和APK大小。由于armeabi-v7a指令集兼容armeabi;市面上的x86手机为了兼容性,基本都使用libhoudini模块,兼容arm指令集;64位机型默认支持32位abi的so,因此在对大小要求比较高的情况下,可以只选择市面上设备基本兼容的armeabi ABI,如果对性能有些许要求,可以再添加x86 ABI。

      2、 NDK_TOOLCHAIN_VERSION(编译器类型、版本)

       默认采用的是GCC编译器,对于GCC版本的选择与NDK版本有关系,本人使用的是NDK R12,在64位ABI默认是GCC 4.9,32位ABI默认是GCC 4.8,当然也可以像上面例子中给出的设置一样,设置clang编译器。

      3、 APP_STL(运行库类型)

       Android NDK 默认使用的是最小支持的C++运行库,如果你需要你的NDK程序中使用STL,则可以设置APP_STL := stlport_static,APP_STL有表二中的几种取值。

Name Explanation
system(default) 系统默认的C++运行库
stlport_static 以静态链接方式使用的sttport版本的STL
stlport_shared 以动态链接方式使用的sttport版本的STL
gnustl_static 以静态链接方式使用的gnustl版本的STL
gnustl_shared 以动态链接方式使用的gnustl版本的STL
gabi++_static 以静态链接方式使用的gabi++
gabi++_shared 以动态链接方式使用的gabi++
c++_static 以静态链接方式使用的LLVM libc++
c++_shared 以动态链接方式使用的LLVM libc++

表二:NDK运行库

       若APK中有多个SO文件用到STL,建议都使用动态方式链接STL,这样可以减小整个APK文件大小。
另外需要注意的是官方提供的NDK运行库除了默认的以外都支持RTTI和异常,然而默认是禁用的,将在下面的Android.mk中说明如何开启。

      4、APP_OPTIM(编译模式)

       “release”模式为默认的,生成的是优化后的二进制;也可以设置为“debug”模式,“debug”模式生成的是未优化二进制,提供很多BUG信息,便于调试和分析。
还有其他配置选项,有兴趣可以查看Application.mk官方文档
 

     二、Android.mk


       Android.mk也是一个轻量级的Makefile,其将C/C++源码组织到一个个module中,module可以是静态库、共享库或者独立的可执行文件, 一个Android.mk文件可以有一个,也可以是多个module,modules之间也可以有依赖关系。

      1、基本概念

       Android.mk中包括NDK提供的宏、变量以及模块描述变量,这些宏、变量以及变量的赋值共同组成了Android.mk文件,其在NDK编译中各尽其责,指导着NDK的编译。
      宏:包括my-dir、all-subdir-makefiles等,通过‘$(call <function>)’来调用,返回文本信息。
      变量:包括CLEAR_VARS、BUILD_SHARED_LIBRARY、TARGET_ARCH等,由NDK编译系统提供,并且在Android.mk文件被解析前就已经存在。Android.mk文件有可能被多次解析,因此每次解析时这些变量的值都有可能不同。
      模块描述变量:Module-description,包括LOCAL_PATH、LOCAL_MODULE、LOCAL_SRC_FILES等LOCAL_前缀变量,这些变量除LOCAL_PATH外,均填写在语句include $(CLEAR_VARS)和include $(BUILD_XXX)之间。
其他Android.mk配置可以查看Android.mk官方文档

      2、基础

      在Android.mk中包括一些很基础的变量,下面的栗子包括了基础的变量,本人将详细说明。

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)


       LOCAL_PATH(当前目录) 
       LOCAL_PATH为模块描述变量,一个Android.mk必须定义LOCAL_PATH,用于定位源文件,在本例中,使用的是编译系统提供的宏“my-dir”(“my-dir”返回最近一次包括Makefile文件路径,通常为当前Android.mk所在目录),用于返回当前目录。
此变量不会被CLEAR_VARS清除,所以每个Android.mk文件只需要定义一次就可以了。

       CLEAR_VARS(变量清除)
       CLEAR_VARS变量由编译系统提供,顾名思义,作用是清除模块变量(在include $(CLEAR_VARS)和include $(BUILD_XXX)之间的LOCAL_XXX模块变量),当然LOCAL_PATH除外。由于所有的编译控制文件都是单一的GNU Make可执行上下文环境中解析,而这个上下文环境中所有的变量都是全局的,所以编译module前需要清理相应变量。

       LOCAL_MODULE(module名称)
       LOCAL_MODULE是Android.mk文件中module的唯一标识,这个名字必须是唯一的,且中间不能有空格。在默认情况下,它决定了生成的文件名,如“hello-jni”对应的动态库名称为libhello-jni.so,然而要索引它时,需要“hello-jni”即可,也可以通过变量LOCAL_MODULE_FILENAME来覆盖这个默认名称。

       LOCAL_SRC_FILES (源码文件)
       LOCAL_SRC_FILES 变量包括C/C++源文件列表,这些源文件会被编译到一个module中,不过也不必列出头文件和包括文件,编译系统会自动为你找打所有需要的依赖关系。值得注意的是linux下路径使用顺斜杠(/)。

       BUILD_SHARED_LIBRARY(动态库编译)
       BUILD_SHARED_LIBRARY是编译器提供的变量,表示编译成动态库,它指向一个GNU Makefile脚本,这个脚本收集从include $(CLEAR_VARS)后所有的LOCAL_XXX变量中定义的所有信息,决定编译什么以及怎么编译。
还有BUILD_STATIC_LIBRARY,和BUILD_SHARED_LIBRARY类似,表示编译成静态库,静态库不会被拷贝到APK中。

       PREBUILT_SHARED_LIBRARY(预编译)
       指向一个编译脚本,用来指定一个预编译动态库.使用此变量时,不像BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY那样,LOCAL_SRC_FILES的值必须是只能有一个指向预编译动态库的路径,如foo/libfoo.so,而不是源文件。如下栗子。

扫描二维码关注公众号,回复: 3872701 查看本文章

include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libtest.so
include $(PREBUILT_SHARED_LIBRARY)


PREBUILD_STATIC_LIBRARY和PREBUILD_SHARED_LIBRARY一样,只不过是用于引用静态库。

      TARGET_ARCH_ABI(目标ABI名称)
      如表一所示,目标ABI名称。若定义了多个ABI,则每次解析Android.mk时,值都不一样,主要使用场景为根本不同的ABI定义不同的文件等。

      3、其他模块变量LOCAL_LDLIBS(链接库)

      用于额外链接选项,所有的库都有“-l”前缀。可同时列出多个库,用空格隔开,例如:

LOCAL_LDLIBS := -llog -ldl

       
      Android NDK默认链接了多个库,不需要显示的添加到LOCAL_LDLIBS中,包括 the standard C libraries,the standard C++ libraries,real-time extensions和 pthread库。同时也提供了一些需要显示添加的库,这些库版本有关系,如表三所示。

Android level Lib Explanation
 
Android-3
-llog Android Log
-lz Zlib Compression Library
-ldl Dynamic Linker Library
Android-4 -lGLESv1_CM OpenGL ES 1.x Library
Android-5 -lGLESv2 OpenGL ES 2.0 Library
Android-8 -ljnigraphics The jnigraphics Library
 
Android-9
-lEGL The EGL graphics library
-lOpenSLES Open ES native audio Library
-landroid Natice Android API
Android-14 -lOpenMAXAL OpenMAX AL natice multimedia Library
Android-18 -lGLESv3 OpenGL ES 3.0 Library
Android-21 -lGLESv3 OpenGL ES 3.1 Library

表三:链接库

        LOCAL_CFLAGS、LOCAL_CPPFLAGS和LOCAL_LDFLAGS(编译、链接标志)

        LOCAL_CFLAGS定义的是在编译C/C++时,传递给编译器的标志集合,LOCAL_CPPFLAGS只支持C++,作用也是传递给编译器一些信息,LOCAL_LDFLAGS是指传递给连接器一些额外的参数。

在NDK开发中难免会用到这些标志位,特别是在优化编译时,下面的是本人在开发中遇到的编译选项。

       ① LOCAL_CPPFLAGS += -fexceptions
       由于NDK编译从R5开始才支持C++异常控制,为了通用性,异常处理默认是禁用的(-fno-exceptions),因此需要在指定module中添加LOCAL_CPPFLAGS += -fexceptions编译选项方可编译带异常处理的C++代码。也可以直接在Application.mk中配置APP_CPPFLAGS += -fexceptions。

       ② LOCAL_CPPFLAGS += -frtti
       从NDK R5开始,NDK也开始支持C++ RTTI了,但为了通用性,所有的C++源文件被构建的时候默认是不支持RTTI的(-fno-rtti),可以通过在Android.mk中添加:LOCAL_CPPFLAGS += -frtti或者在Application.mk添加APP_CPPFLAGS += -frtti来开启RTTI。

       ③ LOCAL_CFLAGS += -fvisibility=hidden
       在NDK开发中,源文件的函数都有一个默认的visibility属性为public,编译生成的so文件中几乎所有的函数名、全局变量名均被导出,其实只需要导出java_com开头的jni函数即可,其他函数不需要暴露出来,在Android.mk中设置LOCAL_CFLAGS += -fvisibility=hidden,就可以隐藏不需要导出的函数,若某个函数需要导出,则添加JNIEXPORT或者__attribute__ ((visibility ("default")))即可。
除了安全,不导出不必要的函数外,还能减小so体积。

       ④ LOCAL_CFLAGS += -ffunction-sections
       不添加此参数时,编译文件.o中代码部分只有.text段,使用此参数,会使每个函数单独有一个段,举个栗子,函数func1()会编译成.text.func1段,虽然段多了,但对链接后代码大小并没有影响。

       ⑤ LOCAL_CFLAGS += -fdata-sections
       同上,每个data都有一个单独的段。

       ⑥ LOCAL_LDFLAGS += -Wl --gc-sections
       -Wl,<option>选项是告诉编译器,将后面选项<option>传递给连接器,-Wl,--gc-sections的意思是使用连接器ld链接时删除不用的段。若使用LOCAL_CFLAGS += -ffunction-sections -fdata-sections,则代码和数据均被分割成不同的段,若某个函数或数据未被任何函数调用,则ld不会链接未被调用的函数,从而减小so文件体积,达到优化so的目的。

       ⑦ LOCAL_LDFLAGS += -fPIC
       PIC(position independent code)用于编译位置无关代码,生成可用于共享库的位置独立代码。若不添加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段内容,这样就导致没使用这个.so,代码段的进程在内核中就会生成这个文件的拷贝。

       ⑧ LOCAL_LDFLAGS += -Wall 
       这个的意思是wring all 意思在编译和链接过程中显示所有警告信息。

       ⑨其他
       若需要了解其他编译标志,可以查看GCC Command Options 文档
 

CFLAGS = -g -O2  -Wall -Werror -Wno-unused

 

编译出现警告性错误unused-but-set-variable,变量定义但没有使用,解决方法:
 增加CFLAGS 或CPPFLAGS参数如下:
  CPPFLAGS=" -Werror -Wno-unused-but-set-variable" || exit 1

 

                                          Gcc总体选项列表

后 缀 名

所对应的语言

-S

只是编译不汇编,生成汇编代码

-E

只进行预编译,不做其他处理

-g

在可执行程序中包含标准调试信息.

-o file

把输出文件输出到file里

-v

打印出编译器内部编译各过程的命令行信息和编译器的版本

-I dir

在头文件的搜索路径列表中添加dir目录

-L dir

在库文件的搜索路径列表中添加dir目录

-static

链接静态库

-llibrary

连接名为library的库文件

   

 

· “-I dir”

正如上表中所述,“-I dir”选项可以在头文件的搜索路径列表中添加dir目录。由于Linux中头文件都默认放到了“/usr/include/”目录下,因此,当用户希望添加放置在其他位置的头文件时,就可以通过“-I dir”选项来指定,这样,Gcc就会到相应的位置查找对应的目录。

比如在“/root/workplace/Gcc”下有两个文件:

#include

int main()

{

     printf(“Hello!!\n”);

     return 0;

}

#include

这样,就可在Gcc命令行中加入“-I”选项:

[root@localhost Gcc] Gcc hello1.c –I /root/workplace/Gcc/ -o hello1

这样,Gcc就能够执行出正确结果。

小知识

在include语句中,“<>”表示在标准路径中搜索头文件,““””表示在本目录中搜索。故在上例中,可把hello1.c的“#include”改为“#include “my.h””,就不需要加上“-I”选项了。

· “-L dir”

选项“-L dir”的功能与“-I dir”类似,能够在库文件的搜索路径列表中添加dir目录。例如有程序hello_sq.c需要用到目录“/root/workplace/Gcc/lib”下的一个动态库libsunq.so,则只需键入如下命令即可:

[root@localhost Gcc] Gcc hello_sq.c –L /root/workplace/Gcc/lib –lsunq –o hello_sq

需要注意的是,“-I dir”和“-L dir”都只是指定了路径,而没有指定文件,因此不能在路径中包含文件名。

另外值得详细解释一下的是“-l”选项,它指示Gcc去连接库文件libsunq.so。由于在Linux下的库文件命名时有一个规定:必须以lib三个字母开头。因此在用-l选项指定链接的库文件名时可以省去lib三个字母。也就是说Gcc在对”-lsunq”进行处理时,会自动去链接名为libsunq.so的文件。

(2)告警和出错选项

Gcc的告警和出错选项如表3.8所示。

                                                        Gcc总体选项列表

选 项

含 义

-ansi 支持符合ANSI标准的C程序
-pedantic 允许发出ANSI C标准所列的全部警告信息
-pedantic-error 允许发出ANSI C标准所列的全部错误信息
-w 关闭所有告警
-Wall 允许发出Gcc提供的所有有用的报警信息
-werror 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程

 

下面结合实例对这几个告警和出错选项进行简单的讲解。

如有以下程序段:

#include

void main()

{

     long long tmp = 1;

     printf(“This is a bad code!\n”);

     return 0;

}

这是一个很糟糕的程序,读者可以考虑一下有哪些问题?

· “-ansi”

该选项强制Gcc生成标准语法所要求的告警信息,尽管这还并不能保证所有没有警告的程序都是符合ANSI C标准的。运行结果如下所示:

[root@localhost Gcc]# Gcc –ansi warning.c –o warning

warning.c: 在函数“main”中:

warning.c:7 警告:在无返回值的函数中,“return”带返回值

warning.c:4 警告:“main”的返回类型不是“int”

可以看出,该选项并没有发现”long long”这个无效数据类型的错误。

· “-pedantic”

允许发出ANSI C标准所列的全部警告信息,同样也保证所有没有警告的程序都是符合ANSI C标准的。其运行结果如下所示:

[root@localhost Gcc]# Gcc –pedantic warning.c –o warning

warning.c: 在函数“main”中:

warning.c:5 警告:ISO C90不支持“long long”

warning.c:7 警告:在无返回值的函数中,“return”带返回值

warning.c:4 警告:“main”的返回类型不是“int”

可以看出,使用该选项查看出了”long long”这个无效数据类型的错误。

· “-Wall”

允许发出Gcc能够提供的所有有用的报警信息。该选项的运行结果如下所示:

[root@localhost Gcc]# Gcc –Wall warning.c –o warning

warning.c:4 警告:“main”的返回类型不是“int”

warning.c: 在函数”main”中:

warning.c:7 警告:在无返回值的函数中,”return”带返回值

warning.c:5 警告:未使用的变量“tmp”

使用“-Wall”选项找出了未使用的变量tmp,但它并没有找出无效数据类型的错误。

另外,Gcc还可以利用选项对单独的常见错误分别指定警告,有关具体选项的含义感兴趣的读者可以查看Gcc手册进行学习。

(3)优化选项

Gcc可以对代码进行优化,它通过编译选项“-On”来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的Gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。

不同的优化级别对应不同的优化处理工作。如使用优化选项“-O”主要进行线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。使用优化选项“-O2”除了完成所有“-O1”级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项“-O3”则还包括循环展开和其他一些与处理器特性相关的优化工作。

虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战。因为代码在经过优化之后,原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句也有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。所以笔者建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

(4)体系结构相关选项

Gcc的体系结构相关选项如表3.9所示。

                                                    Gcc体系结构相关选项列表

选 项

含 义

-mcpu=type 针对不同的CPU使用相应的CPU指令。可选择的type有i386、i486、pentium及i686等
-mieee-fp 使用IEEE标准进行浮点数的比较
-mno-ieee-fp 不使用IEEE标准进行浮点数的比较
-msoft-float 输出包含浮点库调用的目标代码
-mshort 把int类型作为16位处理,相当于short int
-mrtd

强行将函数参数个数固定的函数用ret NUM返回,节省调用函数的一条指令

1、-Wall

是打开警告开关

2、-O

代表默认优化,可选:-O0不优化,-O1低级优化,-O2中级优化,-O3高级优化,-Os代码空间优化

3、-g

是生成调试信息,生成的可执行文件具有和源代码关联的可调试的信息

4、-fopenmp

OpenMp是由OpenMP Architecture Review Board牵头提出的,并已被广泛接受的,用于共享内存并行系统的多处理器程序设计的一套指导性的编译处理方案(Compiler Directive)。OpenMP支持的编程语言包括C语言、C++和Fortran;而支持OpenMp的编译器包括Sun Compiler,GNU Compiler和Intel Compiler等。OpenMp提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMp时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。
5、-D

增加全局宏定义
6、-ffast-math  

浮点优化选项 -ffast-math:极大地提高浮点运算速度

7、-mfloat-abi=softfp 浮点运算

转载自:

http://crash.163.com/#news/!newsId=24

http://blog.chinaunix.net/uid-20672257-id-3408132.html

猜你喜欢

转载自blog.csdn.net/ab6326795/article/details/81284900