专栏首页along的开发之旅代码的“真面目”---如何查看cpp预处理后程序代码
原创

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

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

一、问题缘起

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

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

恐怖的宏定义

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

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

二、定位分析

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

C/C++ 代码编译过程

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

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

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

三、解决方案

1. CMake + make

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

Clion项目结构

分析了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项目都可以用这种方法来获得预处理文件。

2. CMake + ninja

本以为探索到此为止。。。但是当我准备把这套方案挪到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_FLAGSCMAKE_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构建系统,都可以方便快捷的生成预处理文件了。

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

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/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%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

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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何处理前任程序员留下的代码

    作为软件工程师不可避免会遇到的一个场景是:我们在改变或添加一个功能到不是我们创建的、我们不熟悉的、与我们负责的系统部分无关的代码中时,会遇到麻烦。虽然这可能会是...

    非著名程序员
  • 如何处理前任程序员留下的代码

    作为软件工程师不可避免会遇到的一个场景是:我们在改变或添加一个功能到不是我们创建的、我们不熟悉的、与我们负责的系统部分无关的代码中时,会遇到麻烦。虽然这可能会是...

    哲洛不闹
  • 优秀程序员是如何处理糟糕代码的

    优秀程序员是如何处理糟糕代码的 可能你一行不好的代码也从来没有写过。这是有可能的,但在现实中又不太可能。 现实情况是,和这个星球上的其他所有程序员一样,你会产出...

    用户1289394
  • C++系列:编译器是如何工作的

    源代码→ 预处理器→ 编译器→ 汇编程序→ 目标代码→ 链接器→ 可执行文件,最后打包好的文件就可以给电脑去判读运行了。

    小Bob来啦
  • C++ gcc编译过程

    第一步:预处理 将源代码的.c 、.cpp 、.h 等文件包含到一个文件中。在这个过程中会使用一些预处理指令要求编译器使用什么样的方式包含这些文件。预处理结束之...

    老九学堂-小师弟
  • (修订)斩获腾讯微信后台开发offer大神的近1.5W字的面试干货分享

    微信又改版了,为了方便第一时间看到我的推送,请按照下列操作,设置“置顶”:点击上方蓝色字体“程序员乔戈里”-点击右上角“…”-点击“设为星标”。加星标不迷路!

    乔戈里
  • CMake学习笔记

    CMake语法指定了许多变量,可用于帮助您在项目或源代码树中找到有用的目录。其中一些包括:

    肉松
  • ​terracling:前端metalangsys后端uniform backend免编程binding生成式语言系统设想

    本文关键字: 用terra打造更科学的js,cpp,用lua+c分离式模拟JS。terra真正的终身语言,terra最接近编译原理的元语言,cling base...

    minlearn
  • g++入门教程

    g++是GNU开发的C++编译器,是GCC(GNU Compiler Collection)GNU编译器套件的组成部分。另外,gcc是GNU的C编译器。

    Dabelv

扫码关注云+社区

领取腾讯云代金券