由于 Homebrew 的 GTest 最新版本要求最低 C++14,因此取消了 CMAKE_CXX_STANDARD 的锁定。最近客户提交了一个 ticket,表示使用 C++20 编译时出现错误。我接手后修复了一个小问题,但随后发现另一个编译错误让人困惑。省略了之前的错误内容,错误信息显示是包含头文件后导致的。问题排查:首先,我尝试...
记一次 C++20 包含 thread 头文件编译报错问题排查
之前项目使用的是固定的 C++11 标准,即 CMakeLists.txt 中的 CMAKE_CXX_STANDARD 被设置为 11。由于 Homebrew 的 GTest 最新版本要求最低 C++14,因此取消了 CMAKE_CXX_STANDARD 的锁定。最近客户提交了一个 ticket,表示使用 C++20 编译时出现错误。我接手后修复了一个小问题,但随后发现另一个编译错误让人困惑。
省略了之前的错误内容,错误信息显示是包含头文件后导致的。
问题排查:
首先,我尝试了无脑但实用的排除法,将问题源文件的代码反复删减,例如删除其他头文件,最终发现,仅剩一行 #include 代码时仍然报错。为了验证是否为 GCC 的 bug,我手动创建了一个文件,仅包含一行代码,然后使用 g++ -std=c++20 -c foo.cc 编译,没有问题。
由此可见,通过代码排除的方式无法解决问题,很可能是编译命令的问题。因此,我使用了 make VERBOSE=1 命令获取详细的编译命令,最终简化为:
其中 /app 为项目目录,lib/ 为 CMake 子项目目录。
然后我突然就明白了……
项目里为了实现信号量而单独编写了一个 Semaphore 类,位于 Semaphore.h。C++ 头文件并不区分大小写,因此在搜索头文件时,优先找到了 /app/lib/Semaphore.h 而非系统自带的包含 POSIX 信号量头文件的 semaphore.h。GCC 11 中头文件隐式包含了头文件。
加上 -v 选项编译可以更清晰地看到搜索顺序:
当然,这个问题与操作系统有关,macOS 上的 Clang 无法复现,但在 Ubuntu 22.04 中可以使用以下命令复现。
解决方法:
虽然考虑过将 Semaphore.h 重命名,但考虑到项目本身就比较混乱,例如在 tests/ 目录下,包含 lib/ 目录的头文件风格有几种:
因为项目是多人合作的,我也在负责其他项目,无法保证所有 PR review 都到位,加上历史因素,所以上述问题仍然存在。于是趁这个机会,将 lib/ 路径从 tests/ 目录的 target_include_directories 中移除了,这样代码也变得更统一了。
P.S. 之前考虑过引入 clang-tidy 到 CI,但一方面默认配置下扫一遍项目耗时太久,另一方面有太多代码风格要修改。这也不是公司 OKR 花大力度的项目,所以也不想投入过多时间修这个了。
被我忽悠了吗?
其实看到这里,如果觉得我写得没有什么问题,那么恭喜你,被我忽悠了。
首先,从前面我复现的那段代码可以看到,我是在 Docker 容器中复现的。如果你在容器中测试过复现代码,会发现并没有问题:
之前复现我创建的是小写的 semaphore.h 报错,这没问题。但我这里创建了大写的 Semaphore.h 编译仍然通过了。
然而,我进入项目目录中,运行同样的命令时仍然报错:
问题的根源在于,Ubuntu 22.04 容器内,文件名是大小写敏感的,因此可以同时存在 semaphore.h 和 Semaphore.h。然而,我运行容器时,是将 /app 目录映射到了宿主机的项目目录,因此 /app 目录下并不是像 Linux 一样对文件名大小写敏感。
(即便如此,我仍然将 lib/ 目录从 target_include_directories 中移除了,借机把头文件包含的代码风格整理下也是好的)2024-08-13