CMake编译系统详解:面试官问“这个项目怎么编译的”,我愣住了
前面Eigen的两篇聊完了数学库的使用算是过关了。今天说一个每个C项目都绑在一起、但很多人不太重视的东西——CMake。说个真实的事。去年面试一家做工业机器人的公司技术面聊得挺好的算法、C基础、ROS2都说得过去。最后面试官问了句你之前那个项目CMakeLists.txt是你自己写的吗我犹豫了一下说了句大部分是参考模板改的。面试官笑了笑又问那如果让你从零搭一个多模块的机器人项目包含三个可执行文件、两个共享库、还依赖Eigen和PCL你的CMakeLists.txt大概长什么样我当时脑子一片空白。这个问题把我问住了。后来痛补了一遍CMake才发现它是理解C项目结构的一把钥匙。CMake到底是干什么的先说个背景。C代码写完之后需要编译、链接才能运行。编译器需要知道头文件在哪、源文件有哪些、要链接哪些库。小项目手动敲一行g就搞定但真实项目几十个源文件、十几个依赖、还要区分Debug和Release手动编译不现实。CMake做的事情是你用声明式的语法描述项目结构——有哪些目标、依赖什么、编译选项是什么——然后CMake生成对应平台的构建文件。Linux上是MakefileWindows上是Visual Studio工程。这个思路叫元构建系统。从一个最小的例子开始一个最简单的CMakeLists.txt长这样cmake_minimum_required(VERSION 3.10) project(MyRobot) add_executable(my_robot src/main.cpp)三行。第一行指定最低CMake版本第二行定义项目名第三行声明一个可执行文件目标。这就够编译一个单文件的程序了。实际项目当然没这么简单。但所有复杂的CMakeLists.txt本质上都是在这个基础上加东西。加一个库add_library(sensor_lib src/sensor.cpp) target_include_directories(sensor_lib PUBLIC include) add_executable(my_robot src/main.cpp) target_link_libraries(my_robot PRIVATE sensor_lib)这里有两个关键概念add_library定义一个库目标target_link_libraries声明目标之间的依赖关系。CMake会自动处理头文件路径和链接顺序。注意PUBLIC和PRIVATE的区别。PUBLIC表示链接了sensor_lib的目标也会继承它的include路径PRIVATE表示只有sensor_lib自己用。搞不清楚的话要么编译不过要么include路径混乱。引入第三方库的正确姿势机器人项目几乎都会用到第三方库。Eigen、PCL、OpenCV、各种ROS2包。怎么在CMake里找到并链接它们是面试常考的内容。现代CMake推荐用find_packagefind_package(Eigen3 REQUIRED) find_package(PCL REQUIRED COMPONENTS common io filters) add_executable(cloud_processor src/cloud_proc.cpp) target_link_libraries(cloud_processor Eigen3::Eigen ${PCL_LIBRARIES} ) target_include_directories(cloud_processor PRIVATE ${PCL_INCLUDE_DIRS})find_package会在系统里搜索对应库的配置文件xxxConfig.cmake找到之后导入它的目标、头文件路径等信息。REQUIRED表示找不到就报错停止。这里有个坑PCL用的是变量风格PCL_LIBRARIESEigen3提供的是导入目标Eigen3::Eigen。两种风格混用很正常但要搞清楚各自的工作方式。多模块项目的CMake组织机器人软件项目通常是多模块的。典型的目录结构是顶层CMakeLists.txt加几个子目录common、perception、planning、main每个子目录有自己的CMakeLists.txt、include和src。顶层CMakeLists.txt负责全局配置和子目录的引入cmake_minimum_required(VERSION 3.14) project(MyRobotSystem VERSION 1.0.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Eigen3 REQUIRED) find_package(PCL REQUIRED) add_subdirectory(common) add_subdirectory(perception) add_subdirectory(planning) add_subdirectory(main)每个子目录定义各自的库或可执行文件模块之间通过target_link_libraries建立依赖# perception/CMakeLists.txt add_library(perception src/detector.cpp src/tracker.cpp) target_link_libraries(perception PUBLIC common Eigen3::Eigen ${PCL_LIBRARIES} )# main/CMakeLists.txt add_executable(robot_node src/main.cpp) target_link_libraries(robot_node PRIVATE perception planning common )好处是模块化构建——改了perception的代码只重新编译perception和依赖它的模块。项目大了以后增量编译非常关键。面试中的几个高频考点Debug和Release怎么切换用-DCMAKE_BUILD_TYPEDebug或Release。Debug带调试符号不优化Release开启-O3优化。你也可以用target_compile_definitions根据构建类型定义宏比如Debug模式下定义DEBUG_MODE。编译选项怎么设置用target_compile_options而不是全局的add_definitions。前者作用于特定目标不会污染其他模块target_compile_options(perception PRIVATE -Wall -Wextra -O2)安装规则怎么写用install命令指定安装目标和文件包括LIBRARY、ARCHIVE的DESTINATION和头文件目录。这些在ROS2的ament_cmake里都有体现。ament_cmake本质上是对CMake的封装理解了原生CMake看ament_cmake就不会一头雾水。给正在准备面试的你CMake在面试中不算高频考点但系统开发和ROS2岗位经常会被顺带问到。面试官不一定让你手写CMakeLists.txt但会问你的项目是怎么组织的依赖怎么管理的。建议自己动手搭一个多模块的CMake项目能亲自编译并跑起来能解释清楚每个命令的具体作用以及背后的设计原理这样面试的时候就完全够用了。下篇聊C代码调试技巧——GDB、日志、ASAN的实战组合。代码写完了总要调试这几个工具的搭配使用是实际开发中每天都在用的东西。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第62篇 Eigen进阶——四元数、变换矩阵与机器人坐标运算 下一篇预告第64篇 C代码调试技巧——GDB、日志、ASAN的实战组合有任何问题欢迎评论区留言我会尽量回复。