代码的“真面目”---如何查看cpp预处理后程序代码

2020-12-08 21:55:32 浏览数 (1)

cpp中预处理必不可少,如何查看预处理后的程序代码呢?单文件?CMake makefile?CMake ninja?ndk-build? XCode? 答案都在这里。

一、问题缘起

cpp的宏定义,适当的使用既可以减少重复代码,又避免了模板带来的代码膨胀,是很顺手的利器。

但使用宏定义后,宏在预处理阶段才展开,会造成代码阅读的不便;尤其是宏嵌套,会极大加深代码阅读和了解难度。

恐怖的宏定义恐怖的宏定义

用宏封装后,使用起来会非常方便。但是第一次阅读时,会比较难以理解。如果能阅读宏展开后的代码,会轻松方便很多。

所以本文目的就是如何方便快捷的获得宏展开后的代码?

二、定位分析

我们先看下传统编译模型下,源码的编译步骤:

C/C   代码编译过程C/C 代码编译过程

对于单文件,我们可以简单的使用gcc -E 获得预处理文件,使用gcc -S获得汇编文件,其他文件输出详见GCC Options Controlling the Kind of Output。

但是在实际中,项目是由很多个文件组成的,文件间是有依赖关系的;手动确定依赖关系,并输入gcc来编译获得预处理文件,速度慢流程复杂,不具有实际使用意义。

所以需要找个一个方便且能自动帮我们确定依赖关系,直接输出预处理文件的方法。

三、解决方案

1. CMake make

平常验证cpp代码喜欢使用CLion,CLion默认使用CMake make构建系统,项目结构如下:

Clion项目结构Clion项目结构

分析了CMake默认生成的makefile,意外发现里面就有我需要的target。

target “main.cpp.i”,其内容如下,作用是生成预处理preprocess文件。

代码语言:txt复制
# 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"。

代码语言:txt复制
# 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项目都可以用这种方法来获得预处理文件。

2. CMake ninja

本以为探索到此为止。。。但是当我准备把这套方案挪到Android NDK项目上时,才忽然意识到,Android NDK项目是基于CMake ninja构建系统,不是CMake makefile这套。

最初想的是在ninja中找到makefile对应的预处理构建任务,然后用ninja来执行这些预处理构建任务。但是查询资料后发现,ninja为了提升构建速度,既没有默认生成这些中间文件,也没有生成这些中间文件的任务。同时gcc/clang最新的构建流程中,也不会生成这些中间文件。

继续探索,幸运的发现gcc的Debugging-Options有一个选项-save-temps,意如其名,保存临时文件,预处理和汇编都是生成object的中间临时文件。

代码语言:txt复制
-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_FLAGSCMAKE_CXX_FLAGS;前者是对c文件生效,后者是对cpp文件生效。

代码语言:txt复制
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构建系统,都可以方便快捷的生成预处理文件了。

3. ndk-build Android.mk

但是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起作用,所以我们只需要设置这个就可。

代码语言:txt复制
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的两种构建系统,我们都可以快速生成预处理文件了。

4. XCode

最后看下在iOS的XCode中,如何查看cpp预处理文件?

XCode中查看预处理文件非常方便和优雅。

选中文件后,只需点击Product/Perform Action,即可看到Preprocess/Assemble,点击执行即可生成。

不过必须选中.cpp才有用, 在选中.h/.hpp时试了都是无效的。

Preprocess/AssemblePreprocess/Assemble

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 Options

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 Options

https://clang.llvm.org/docs/CommandGuide/clang.html

https://gcc.gnu.org/onlinedocs/gcc-3.4.0/gcc/Option-Summary.html#Option Summary

https://clang.llvm.org/docs/index.html#

0 人点赞