先定位失败发生在哪一层
跳转到“先定位失败发生在哪一层”从源代码到运行中的进程,要经过语言处理、目标文件生成、链接和加载。IDE 的补全/检查系统还可能单独解析一次代码。错误出现在同一张“错误列表”里,并不代表它们由同一个工具产生。
| 阶段 | 输入与工作 | 典型故障 |
|---|---|---|
| 预处理与头文件查找 | 处理包含、宏和条件编译 | 文件不存在、包含目录不正确、宏选中了错误分支 |
| 编译 | 解析语言、检查类型并生成目标代码所需表示 | 名称未声明、语法或类型错误 |
| 汇编 | 将汇编表示变为可重定位目标文件 | 目标架构或汇编指令不匹配 |
| 链接 | 组合目标文件和库,处理符号与重定位 | 未定义引用、重复定义、库或架构不匹配 |
| 加载与初始化 | 读取可执行文件、装入依赖、执行启动逻辑 | 找不到 DLL/SO、符号版本或入口点不兼容 |
| 运行与结束 | 执行业务、管理资源并退出 | 逻辑错误、越界、异常或外部故障 |
GCC 的 -E、-S、-c 可以停在不同阶段。实际工具链可能通过内存或管道传递中间结果;没有看到 .i/.s 文件不代表没有这些处理。GCC 通常把预处理后的 C 写成 .i、C++ 写成 .ii;C 和 C++ 的前端也不是同一个程序。GCC 总体选项
一个可以逐阶段检查的 C++17 项目
跳转到“一个可以逐阶段检查的 C++17 项目”创建 include/demo/build_choice.h:
#ifndef DEMO_BUILD_CHOICE_H#define DEMO_BUILD_CHOICE_H
#if defined(DEMO_BONUS)inline constexpr int selected_base = 40;#elseinline constexpr int selected_base = 1;#endif
int adjustment() noexcept;
#endifadjustment.cpp 提供声明所对应的定义:
#include "demo/build_choice.h"
int adjustment() noexcept{ return 3;}main.cpp 使用该接口:
#include "demo/build_choice.h"#include <cassert>#include <cstdio>
int main(){ const int value = selected_base + adjustment();#if defined(DEMO_BONUS) assert(value == 43);#else assert(value == 4);#endif std::printf("value=%d\n", value);}在项目目录执行下列 GCC 命令。它们显式保留本次检查需要的中间文件:
g++ -std=c++17 -DDEMO_BONUS -I include -E main.cpp -o main.iig++ -std=c++17 -S main.ii -o main.sg++ -c main.s -o main.og++ -std=c++17 -DDEMO_BONUS -I include -c adjustment.cpp -o adjustment.og++ main.o adjustment.o -o demoWindows MinGW 生成 demo.exe,Linux 下上述输出为 demo;分别运行 ./demo.exe 或 ./demo,应得到 value=43。不要用 gcc 代替最后一行的 C++ 驱动,期待它自动补齐所有 C++ 标准库依赖。
这组例子还能区分几个问题:
- 从预处理命令去掉
-I include,头文件查找失败,后面的链接根本还没有开始。 - 只链接
main.o,adjustment()缺少定义,属于链接失败;重复增加头文件包含不能生成这个定义。 - 去掉所有翻译单元的
-DDEMO_BONUS后重新生成中间文件,结果变为4。 - 使用
-DDEMO_BONUS=0,defined(DEMO_BONUS)仍然成立,结果仍为43。检测“存在”和检测宏的数值不是同一种条件。
各翻译单元必须采用一致的接口配置,尤其不要让同一头文件中的定义因不一致的宏而违反单一定义规则。修改宏或头文件后,要重新生成受影响的目标文件,再链接。
保留原 OpenCV 条件编译场景
跳转到“保留原 OpenCV 条件编译场景”原文使用 BATCHED_NMS 选择 OpenCV DNN 的 NMS API:定义该宏时调用 cv::dnn::NMSBoxesBatched,否则调用 cv::dnn::NMSBoxes。这是编译时选择,未选中的源分支不会进入后续 C++ 编译。
两个接口都可以处理多个候选框。NMSBoxesBatched 的关键额外输入是 class_ids,用于按类别处理抑制;不能把它概括成“多个框”,再把 NMSBoxes 概括成“单个框”。原片段还依赖 bboxes/scores/labels、阈值、输出索引和匹配版本的 OpenCV 模块,因此不是独立完整程序。本页验证的是上面的构建阶段例子,未声称运行了该 OpenCV 工程。OpenCV NMS API
E1696 与“启用 Windows 运行时扩展”
跳转到“E1696 与“启用 Windows 运行时扩展””另一条原记录只给出了 Visual Studio 中“使用 Windows 运行时扩展→是”的做法,没有提供缺失的头文件和项目类型。这个设置对应 /ZW,用于 C++/CX Windows Runtime 扩展,不是普通 C++ 项目通用的头文件修复开关。只有工程确实采用相应语言/平台模型时,才应按它的构建要求配置。MSVC /ZW
看到 E1696“无法打开源文件”时,先区分 IntelliSense 与真正构建输出,再检查:
- 错误中具体缺失的文件是否存在,名称、大小写和路径是否正确。
- 当前配置/平台的编译命令是否具有对应包含目录,所需 SDK 或生成头文件是否已就绪。
- 若命令行构建成功,IDE 的解析配置是否与该命令一致;Makefile 项目和 CMake 项目应检查各自的配置来源。
库目录和链接输入解决不了预处理器找不到头文件的问题;反过来,增加包含目录也不能修复缺失库实现。重扫 IDE 数据库适合配置已正确而缓存仍旧的情形,不能替代补齐实际依赖。Visual Studio IntelliSense 配置
入口点、main 与资源清理
跳转到“入口点、main 与资源清理”操作系统开始执行的是二进制规定的入口点。普通托管 C/C++ 应用通常先经过运行库启动和初始化,再调用 main;不能把加载器的入口点直接等同于 main。嵌入式或独立环境还有自己的启动约定。
正常从 main 返回会经历相应的 C++ 正常终止流程,但异常终止、强制结束进程等路径有不同语义。操作系统回收进程内存也不代表应用已正确保存文件、完成网络协议或释放所有外部资源。编译器、链接器、加载器各解决一部分问题;构建工具组织这些步骤,调试器和版本控制则负责其他工作。