cpp中预处理必不可少,如何查看预处理后的程序代码呢?单文件?CMake+makefile?CMake+ninja?ndk-build? XCode? 答案都在这里。
cpp的宏定义,适当的使用既可以减少重复代码,又避免了模板带来的代码膨胀,是很顺手的利器。
但使用宏定义后,宏在预处理阶段才展开,会造成代码阅读的不便;尤其是宏嵌套,会极大加深代码阅读和了解难度。
用宏封装后,使用起来会非常方便。但是第一次阅读时,会比较难以理解。如果能阅读宏展开后的代码,会轻松方便很多。
所以本文目的就是如何方便快捷的获得宏展开后的代码?
我们先看下传统编译模型下,源码的编译步骤:
对于单文件,我们可以简单的使用gcc -E
获得预处理文件,使用gcc -S
获得汇编文件,其他文件输出详见GCC Options Controlling the Kind of Output。
但是在实际中,项目是由很多个文件组成的,文件间是有依赖关系的;手动确定依赖关系,并输入gcc来编译获得预处理文件,速度慢流程复杂,不具有实际使用意义。
所以需要找个一个方便且能自动帮我们确定依赖关系,直接输出预处理文件的方法。
平常验证cpp代码喜欢使用CLion,CLion默认使用CMake + make构建系统,项目结构如下:
分析了CMake默认生成的makefile,意外发现里面就有我需要的target。
target “main.cpp.i”,其内容如下,作用是生成预处理preprocess文件。
# target to preprocess a source file
main.cpp.i:
$(MAKE) -f CMakeFiles/cppConcurrencyDemo.dir/build.make CMakeFiles/cppConcurrencyDemo.dir/main.cpp.i
进入命令行,和makefile同级别目录,然后执行“make main.cpp.i”,就会生成对应的preprocess文件。
其支持的target如下,可以看到除了生成预处理文件,还有生成汇编的target "main.s"。
# Help Target
help:
@echo "The following are some of the valid targets for this Makefile:"
@echo "... all (the default if no target is provided)"
@echo "... clean"
@echo "... depend"
@echo "... rebuild_cache"
@echo "... edit_cache"
@echo "... cppConcurrencyDemo"
@echo "... main.o"
@echo "... main.i"
@echo "... main.s"
总结:由于是借助于CMake+makefile的能力,所以理论上所有CMake+makefile项目都可以用这种方法来获得预处理文件。
本以为探索到此为止。。。但是当我准备把这套方案挪到Android NDK项目上时,才忽然意识到,Android NDK项目是基于CMake+ninja构建系统,不是CMake+makefile这套。
最初想的是在ninja中找到makefile对应的预处理构建任务,然后用ninja来执行这些预处理构建任务。但是查询资料后发现,ninja为了提升构建速度,既没有默认生成这些中间文件,也没有生成这些中间文件的任务。同时gcc/clang最新的构建流程中,也不会生成这些中间文件。
继续探索,幸运的发现gcc的Debugging-Options有一个选项-save-temps
,意如其名,保存临时文件,预处理和汇编都是生成object的中间临时文件。
-save-temps
Store the usual "temporary" intermediate files permanently;
place them in the current directory and name them based on the source file.
Thus, compiling foo.c with -c -save-temps would produce files foo.i and foo.s, as well as foo.o.
This creates a preprocessed foo.i output file even though the compiler now normally uses an integrated preprocessor.
没毛病,给CMake加上这个参数,看下效果。
因为使用的是CMake,需要设置CMAKE_C_FLAGS
和CMAKE_CXX_FLAGS
;前者是对c文件生效,后者是对cpp文件生效。
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -save-temps")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -save-temps")
运行,rg -uuu --file | rg \.s
,果然找到了生成的预处理文件,大功告成。
进一步查找,发现-save-temps
还可以跟一个参数-save-temps=obj
,表示生成预处理文件的位置和.o
同目录,这样会更便于查看。
而且这个参数是gcc/clang都支持的。
到这一步,对于所有的CMake+gcc/clang构建系统,都可以方便快捷的生成预处理文件了。
但是Android NDK还有legacy NDK构建系统 ndk-build,配合魔改过的Android.mk。这种构建方式支持生成预处理文件么?
既然我们都知道gcc/clang的编译参数-save-temps=obj
,那么只要把这个选项设置进c和cxx的编译参数中即可。
Android.mk中LOCAL_CFLAGS/LOCAL_CPPFLAGS
和CMake中的CMAKE_C_FLAGS/CMAKE_CXX_FLAGS
参数类似,只是 LOCAL_CFLAGS
同时对c和cpp起作用,所以我们只需要设置这个就可。
LOCAL_CFLAGS := -save-temps=obj
此时就可以在hello-jni/app/build/intermediates/ndkBuild/debug/obj/local/arm64-v8a/objs-debug/hello-jni/hello-jni.i
找到生成的预处理文件。
到这里,对Android NDK的两种构建系统,我们都可以快速生成预处理文件了。
最后看下在iOS的XCode中,如何查看cpp预处理文件?
XCode中查看预处理文件非常方便和优雅。
选中文件后,只需点击Product/Perform Action
,即可看到Preprocess/Assemble
,点击执行即可生成。
不过必须选中.cpp
才有用, 在选中.h/.hpp
时试了都是无效的。
XCode 生成预编译相当简单,但是在CMake构建系统中摸爬滚打,也让我们找到了非常多的乐趣。
到这里,对于Android、iOS涉及cpp时,生成预处理文件我们都有了方案,探索到此结束,共勉。
参考:
https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html
https://www.cnblogs.com/Wayou/p/macros_in_c_and_cpp.html
https://gcc.gnu.org/onlinedocs/gcc-3.4.0/gcc/Overall-Options.html#Overall%20Options
http://anadoxin.org/blog/generating-preprocessed-sources-in-cmake-projects.html
https://gcc.gnu.org/onlinedocs/gcc-3.4.0/gcc/Debugging-Options.html#Debugging%20Options
https://clang.llvm.org/docs/CommandGuide/clang.html
https://gcc.gnu.org/onlinedocs/gcc-3.4.0/gcc/Option-Summary.html#Option%20Summary
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。