游戏引擎学习第16天
视频参考:https://www.bilibili.com/video/BV1mEUCY8EiC/
这些字幕讨论了编译器警告的概念以及如何在编译过程中启用和处理警告。以下是字幕的内容摘要:
-
警告的定义:警告是编译器用来告诉你某些地方可能存在问题,尽管编译器不强制要求你修复它们。警告并不会阻止编译过程,但它们通常提示代码中可能存在潜在问题。
-
警告级别:警告通常分为不同的级别,例如1级、2级、3级、4级等。你可以选择启用一个警告级别,以便只关注特定类型的警告,而不是每一个单独的警告。
-
启用警告:作者提到通过启用较高级别的警告,可以让编译器显示更多的警告信息,帮助开发者发现潜在问题。
-
解决警告:一些警告是可以修复的,而其他警告则可能是由于外部库或系统文件(如
windows.h
)引起的,在这些情况下可能无法轻易修复。 -
警告级别的调整:如果某些警告级别的警告过于繁琐,可以通过降低警告级别来减少警告的数量,从而使得编译过程更加顺利。
-
具体示例:提到了一些警告信息,例如编译器告诉开发者结构体中可能插入了填充,或者警告与 Windows 头文件相关的问题。
总结起来,这段对话讨论了编译器警告的启用、调整和处理策略,帮助开发者理解如何更有效地管理编译时的警告信息。
在MSVC(Microsoft Visual C++)中,警告级别用于控制编译器输出的警告信息的详细程度。MSVC提供了四个警告级别,分别为:
- 级别 0 (
/W0
):关闭所有警告。适用于不希望看到任何警告的情况,但不建议在开发中使用,因为可能会错过重要的提示。 - 级别 1 (
/W1
):显示最低级别的警告。适用于仅查看最严重警告的情况。 - 级别 2 (
/W2
):显示常见的警告信息,通常是开发过程中需要注意的警告。 - 级别 3 (
/W3
):显示更多警告信息,是默认的警告级别。适用于大多数开发场景。 - 级别 4 (
/W4
):显示所有警告,包括那些可能无关紧要的。适用于希望尽可能详细地查看警告信息的情况。 - 级别 5 (
/Wall
):显示所有警告,包括非常详细的信息。用于调试时极为详尽的检查。
cmake 中添加警告参数
# 为 MSVC 编译器添加参数
if(MSVC) # 如果编译器是 MSVC(Microsoft Visual C++)
# 设置 C++ 编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX /W4 /wd4819")
# /WX : 将所有警告视为错误(会让编译因警告失败)
# /W4 : 设置警告级别为 4,显示大多数警告
# /wd4819 : 屏蔽警告 C4819,避免文件编码问题导致的警告
endif()
下面是解决相关的警告
C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp
[build] cl: 命令行 warning D9025 :正在重写“/W3”(用“/W4”)
[build] C:\Users\16956\Documents\game\day16\game\game\game.h(134): error C2220: 以下警告被视为错误
[build] C:\Users\16956\Documents\game\day16\game\game\game.h(134): warning C4201: 使用了非标准扩展: 无名称的结构/联合
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(110): warning C4244: “参数”: 从“uint64”转换到“DWORD”,可能丢失数据
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(27): warning C4244: “初始化”: 从“int”转换到“uint8”,可能丢失数据
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(28): warning C4244: “初始化”: 从“int”转换到“uint8”,可能丢失数据
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(161): warning C4100: “pState”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(161): warning C4100: “dwUserIndex”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(167): warning C4100: “pVibration”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(167): warning C4100: “dwUserIndex”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(383): warning C4100: “Height”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(383): warning C4100: “Width”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(383): warning C4100: “Y”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(382): warning C4100: “X”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(573): warning C4100: “cmdshow”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(573): warning C4100: “cmdline”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(572): warning C4100: “hInstPrev”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(619): warning C4189: “xOffset”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(620): warning C4189: “yOffset”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(878): warning C4189: “MillisecondPerFrame”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(892): warning C4189: “Temp”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(881): warning C4189: “FPS”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(882): warning C4189: “MCPF”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(725): warning C4189: “Left”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(724): warning C4189: “Down”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(726): warning C4189: “Right”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(723): warning C4189: “Up”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(35): warning C4505: “GameStartup”: 已删除具有内部链接的未引用函数
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(45): warning C4505: “GameShutDown”: 已删除具有内部链接的未引用函数
[build] ninja: build stopped: subcommand failed.
[build] C:\Users\16956\Documents\game\day16\game\game\game.h(134): warning C4201: 使用了非标准扩展: 无名称的结构/联合
warning C4201
是 MSVC 中的一个警告,它提示你使用了非标准扩展:无名称的结构或联合。这意味着在你的代码中,你定义了一个没有名称的匿名结构体或联合体。
IDE 显示的是一个插件
解决上面的警告
问题出现在 union
内部的结构体部分:
union {
game_button_state Button[6]; // 按钮状态(最多 6 个按钮)
struct {
game_button_state Up; // 上方向键状态
game_button_state Down; // 下方向键状态
game_button_state Left; // 左方向键状态
game_button_state Right; // 右方向键状态
game_button_state LeftShoulder; // 左肩键状态
game_button_state RightShoulder; // 右肩键状态
};
};
这里,struct
是一个匿名结构体,没有给它命名。因此,这会触发 warning C4201
,因为这种匿名结构体的使用违反了标准的 C++ 规则,虽然 MSVC 允许它作为扩展。
如何理解这个警告
C++ 标准要求结构体或联合体应当具备一个名字,便于在后续的代码中引用。匿名结构体或联合体虽然在某些编译器中被允许,但它们并不符合标准,因此会触发 C4201
警告。
如何解决
要解决这个问题,你需要给匿名的结构体命名。可以通过以下方式修改你的代码:
union {
game_button_state Button[6]; // 按钮状态(最多 6 个按钮)
struct game_controller_buttons {
game_button_state Up; // 上方向键状态
game_button_state Down; // 下方向键状态
game_button_state Left; // 左方向键状态
game_button_state Right; // 右方向键状态
game_button_state LeftShoulder; // 左肩键状态
game_button_state RightShoulder; // 右肩键状态
};
};
在上面的代码中,game_controller_buttons
是给结构体命名的名称,这样就不会触发 C4201
警告了。
总结
warning C4201
提示你在代码中使用了匿名结构体或联合体,这可能是编译器的扩展功能,但不符合 C++ 标准。通过为结构体或联合体命名,可以解决这个警告。
这个警告我们不认为是一个错误
set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} /WX /W4 /wd4819 /wdc4201”)
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(110): warning C4244: “参数”: 从“uint64”转换到“DWORD”,可能丢失数据
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(27): warning C4244: “初始化”: 从“int”转换到“uint8”,可能丢失数据
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(28): warning C4244: “初始化”: 从“int”转换到“uint8”,可能丢失数据
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(161): warning C4100: “pState”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(161): warning C4100: “dwUserIndex”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(167): warning C4100: “pVibration”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(167): warning C4100: “dwUserIndex”: 未引用的形参
warning C4100
是 MSVC 的一个警告,表明某个函数参数未被使用。在你的代码中,这些未使用的参数是 pState
、pVibration
和 dwUserIndex
,它们出现在以下打桩函数中:
X_INPUT_GET_STATE(XInputGetStateStub) {
return (ERROR_DEVICE_NOT_CONNECTED);
}
X_INPUT_SET_STATE(XInputSetStateStub) {
return (ERROR_DEVICE_NOT_CONNECTED);
}
由于打桩函数的实现仅返回 ERROR_DEVICE_NOT_CONNECTED
,而没有使用这些参数,因此触发了 C4100
警告。
解决方法
有几种方法可以消除这个警告:
方法 1:显式地标记未使用的参数
可以通过显式引用参数来避免警告,即使没有实际使用它们:
X_INPUT_GET_STATE(XInputGetStateStub) {
(void)dwUserIndex; // 明确声明未使用
(void)pState; // 明确声明未使用
return (ERROR_DEVICE_NOT_CONNECTED);
}
X_INPUT_SET_STATE(XInputSetStateStub) {
(void)dwUserIndex; // 明确声明未使用
(void)pVibration; // 明确声明未使用
return (ERROR_DEVICE_NOT_CONNECTED);
}
这种方式简单直接,表明你有意不使用这些参数。
方法 2:使用 MSVC 特定的宏
MSVC 提供了一个 UNREFERENCED_PARAMETER
宏,可以标记未使用的参数。它会自动消除警告:
#include <windows.h> // 包含 UNREFERENCED_PARAMETER 宏
X_INPUT_GET_STATE(XInputGetStateStub) {
UNREFERENCED_PARAMETER(dwUserIndex);
UNREFERENCED_PARAMETER(pState);
return (ERROR_DEVICE_NOT_CONNECTED);
}
X_INPUT_SET_STATE(XInputSetStateStub) {
UNREFERENCED_PARAMETER(dwUserIndex);
UNREFERENCED_PARAMETER(pVibration);
return (ERROR_DEVICE_NOT_CONNECTED);
}
方法 3:修改函数签名
如果这些参数完全无用(仅用于符合接口定义),可以使用未命名的参数形式:
X_INPUT_GET_STATE(XInputGetStateStub) {
return (ERROR_DEVICE_NOT_CONNECTED);
}
X_INPUT_SET_STATE(XInputSetStateStub) {
return (ERROR_DEVICE_NOT_CONNECTED);
}
然而,如果这些参数在其他上下文中需要使用,不推荐这种方式。
方法 4:全局屏蔽警告
如果这些警告在项目中普遍存在,也可以在编译器选项或代码中禁用 C4100
警告:
-
CMake 中添加编译选项:
if(MSVC) add_compile_options(/wd4100) # 禁用 C4100 警告 endif()
-
在代码中使用
#pragma
:#pragma warning(push) #pragma warning(disable: 4100) X_INPUT_GET_STATE(XInputGetStateStub) { return (ERROR_DEVICE_NOT_CONNECTED); } X_INPUT_SET_STATE(XInputSetStateStub) { return (ERROR_DEVICE_NOT_CONNECTED); } #pragma warning(pop)
总结
推荐优先使用 方法 1 或 方法 2,显式标记未使用的参数,既解决了警告,也保留了代码的可读性。如果打桩函数广泛使用且参数确实无用,可以考虑 方法 4 全局屏蔽。
c++ 属性
在 C++ 中,除了使用编译器特定的宏(如 MSVC 的 UNREFERENCED_PARAMETER
),还可以利用标准或扩展的函数属性来表明某个参数未被使用,从而避免警告。以下是常见的实现方式:
1. 使用 C++17 的 [[maybe_unused]]
属性
从 C++17 开始,标准引入了 [[maybe_unused]]
属性,用于标记未被使用的变量或参数。这样可以告诉编译器,此参数未被使用是有意的,不需要警告。
X_INPUT_GET_STATE(XInputGetStateStub) {
[[maybe_unused]] DWORD dwUserIndex; // 参数标记为未使用
[[maybe_unused]] XINPUT_STATE* pState;
return (ERROR_DEVICE_NOT_CONNECTED);
}
也可以直接在参数列表中使用:
X_INPUT_GET_STATE(XInputGetStateStub([[maybe_unused]] DWORD dwUserIndex, [[maybe_unused]] XINPUT_STATE* pState)) {
return (ERROR_DEVICE_NOT_CONNECTED);
}
- 优点:标准化,跨编译器支持。
- 缺点:需要使用 C++17 或更新版本。
2. 使用编译器特定的属性
不同编译器提供了自己的方式来标记未使用的参数:
(a) GCC 和 Clang: __attribute__((unused))
适用于 GCC 和 Clang,可以通过 __attribute__((unused))
标记参数:
X_INPUT_GET_STATE(XInputGetStateStub(DWORD __attribute__((unused)) dwUserIndex, XINPUT_STATE* __attribute__((unused)) pState)) {
return (ERROR_DEVICE_NOT_CONNECTED);
}
或者用在局部变量上:
X_INPUT_GET_STATE(XInputGetStateStub) {
DWORD dwUserIndex __attribute__((unused));
XINPUT_STATE* pState __attribute__((unused));
return (ERROR_DEVICE_NOT_CONNECTED);
}
(b) MSVC: __pragma(warning(suppress: 4100))
MSVC 提供了特定的 __pragma
语法来抑制特定警告:
X_INPUT_GET_STATE(XInputGetStateStub) {
__pragma(warning(suppress: 4100)) DWORD dwUserIndex;
__pragma(warning(suppress: 4100)) XINPUT_STATE* pState;
return (ERROR_DEVICE_NOT_CONNECTED);
}
3. 使用通用技巧
如果无法使用上述属性,可以使用一些编程技巧,告诉编译器这是有意未使用的参数:
(a) 强制类型转换
通过将参数强制转换为 void
,可以消除未使用参数的警告:
X_INPUT_GET_STATE(XInputGetStateStub) {
(void)dwUserIndex; // 明确声明未使用
(void)pState; // 明确声明未使用
return (ERROR_DEVICE_NOT_CONNECTED);
}
(b) 使用 [[nodiscard]]
配合
对于返回值函数,可以结合 [[nodiscard]]
属性强调参数未被使用,但函数仍需要处理。
总结
- 如果项目使用 C++17 或更新版本,优先使用
[[maybe_unused]]
,这是标准化的方式。 - 如果需要支持 旧版本编译器:
- GCC/Clang:使用
__attribute__((unused))
。 - MSVC:使用
__pragma
或UNREFERENCED_PARAMETER
宏。
- GCC/Clang:使用
- 若无需依赖编译器特性,可使用
(void)
转换法,这是一种跨平台兼容的解决方案。
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(383): warning C4100: “Height”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(383): warning C4100: “Width”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(383): warning C4100: “Y”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(382): warning C4100: “X”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(573): warning C4100: “cmdshow”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(573): warning C4100: “cmdline”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(572): warning C4100: “hInstPrev”: 未引用的形参
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(619): warning C4189: “xOffset”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(620): warning C4189: “yOffset”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(878): warning C4189: “MillisecondPerFrame”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(892): warning C4189: “Temp”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(881): warning C4189: “FPS”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(882): warning C4189: “MCPF”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(725): warning C4189: “Left”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(724): warning C4189: “Down”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(726): warning C4189: “Right”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(723): warning C4189: “Up”: 局部变量已初始化但不引用
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(35): warning C4505: “GameStartup”: 已删除具有内部链接的未引用函数
[build] C:\Users\16956\Documents\game\day16\game\game\game.cpp(45): warning C4505: “GameShutDown”: 已删除具有内部链接的未引用函数
[build] C:\Users\16956\Documents\game\day16\game\game\win32_game.cpp(844) : warning C4701: 使用了可能未初始化的局部变量“ByteToLock”
14:10 常用使用的其他命令行选项
在 Visual Studio 中,-FC
和 -Zi
是用于编译器的两个选项,分别用于控制文件路径的显示和调试信息的生成。
-FC
参数
全称:Full Path to Source Code Files
- 作用:启用此选项后,编译器在生成的调试信息中会记录源代码文件的完整路径,而不仅仅是文件名。
- 用途:
- 在调试或错误日志中,完整路径有助于区分同名但位于不同目录的文件。
- 对于大型项目或涉及多层文件夹的项目,使用
-FC
可以减少文件路径冲突引起的歧义。
-Zi
参数
全称:Generate Program Database for Debugging
- 作用:启用此选项后,编译器会生成包含详细调试信息的 PDB(Program Database)文件。
- 用途:
- 使调试工具(如 Visual Studio 调试器)能够通过 PDB 文件查看代码中的变量、函数、调用堆栈等信息。
- 是调试模式下的默认配置,用于支持断点调试、单步执行和查看源代码。
综合使用
-FC
通常与 -Zi
一起使用,尤其在调试场景中:
- 完整路径(
-FC
)有助于清楚地定位源代码文件。 - 调试信息(
-Zi
)提供详细的调试能力。
示例
如果你在 VS 中手动编译代码,可以添加如下命令:
cl -Zi -FC source.cpp
这将生成包含完整路径的调试信息文件(.pdb
文件)。
在 Visual Studio 的编译器中,-Oi
是一个优化选项,用于启用 内联函数调用,提高程序的性能。
作用
-Oi
参数启用编译器对特定内建函数(intrinsic functions)的内联替换。
- 内建函数(Intrinsic Functions):是编译器提供的一些特殊函数,通常映射到特定的 CPU 指令。例如,
memcpy
、strcmp
或数学函数如sqrt
。 - 启用
-Oi
后,这些函数在编译时被替换为等效的高效汇编指令,而不是通过函数调用完成任务。
优点
- 性能提升:
- 减少函数调用的开销(如栈操作和返回值处理)。
- 提高执行效率,因为直接使用了 CPU 指令。
- 优化代码大小:
- 内联代码有时能减少代码膨胀,尤其是对于小函数。
- 使用高级指令:
- 编译器可能使用更高效的 CPU 指令来实现特定功能。
可能的缺点
- 代码大小增加:
- 如果过多的函数被内联,可能导致生成的代码体积增大。
- 兼容性问题:
- 某些内建函数可能与特定硬件架构相关,因此需要确保目标硬件支持。
- 调试复杂性:
- 内联后的代码可能难以逐步调试,因为它不再是显式的函数调用。
使用场景
- 适用:高性能应用,尤其是对数学计算、字符串操作等性能敏感的场景。
- 不适用:代码大小有限制或者需要清晰的调试信息时。
相关指令
-Oi
通常与其他优化选项结合使用:
-O2
:最大化优化性能(包括-Oi
)。-Ox
:极致优化(也启用-Oi
)。-Ob
:控制内联的函数数量。
示例
编译命令:
cl -Oi source.cpp
代码示例:
编译器可能将以下代码:
#include <math.h>
double square_root(double x) {
return sqrt(x);
}
转为内联的汇编指令,而不是调用标准库函数 sqrt
。
总结:使用 -Oi
能提升性能,但需根据项目需求权衡性能与代码体积之间的关系。
在 Visual Studio 编译器中,-GR-
和 -EHa-
是控制运行时功能和异常处理机制的选项,它们的作用如下:
-GR-
参数
作用:禁用 RTTI(Run-Time Type Information)。
RTTI 简介
- RTTI 是 C++ 提供的一种运行时机制,允许程序在运行时动态查询对象的类型信息(例如使用
typeid
和dynamic_cast
)。 - 启用 RTTI(默认设置,
-GR
)会在程序中生成额外的类型信息。
禁用 RTTI 的效果
- 减少代码大小:避免生成额外的类型信息,可以降低可执行文件的体积。
- 提升性能:避免运行时类型检查带来的额外开销。
- 限制功能:
typeid
和dynamic_cast
无法使用,程序中尝试调用它们会导致编译错误。
使用场景
- 适用于不依赖 RTTI 的代码,尤其是性能和代码大小敏感的项目(例如嵌入式开发)。
- 代码完全避免了多态和运行时类型查询。
-EHa-
参数
作用:禁用对结构化异常处理(SEH)和 C++ 异常的支持。
SEH 和 C++ 异常简介
- SEH:Windows 特有的异常机制,捕获系统级异常(如访问冲突)。
- C++ 异常:C++ 标准支持的异常机制(
try-catch
块)。
禁用异常支持的效果
- 减少代码大小:禁用异常后,程序不需要生成额外的异常处理表。
- 提升性能:减少异常支持相关的运行时检查。
- 限制功能:所有
try-catch
块、throw
表达式以及捕获系统异常的能力都不可用。
风险
- 禁用异常后,程序中发生未捕获的错误将直接导致崩溃。
- 开发者需要依赖其他方式(如错误码)来处理异常情况。
使用场景
- 适合不依赖异常处理的小型项目或性能敏感的嵌入式系统。
- 项目中已经采用其他机制(如返回值或状态码)代替异常处理。
两者对比与结合
参数 | 功能 | 适用场景 | 限制 |
---|---|---|---|
-GR- | 禁用 RTTI,减少运行时类型信息 | 性能优化或不使用动态类型查询时 | 禁用 typeid 和 dynamic_cast |
-EHa- | 禁用 C++ 异常和 SEH,减少异常支持开销 | 不依赖异常处理的性能优化场景 | 无法使用 try-catch 和异常机制 |
结合使用
-GR- -EHa-
通常在对性能和代码大小极为敏感的项目中一起使用,例如嵌入式开发或实时系统。
示例
编译命令:
cl -GR- -EHa- source.cpp
代码限制示例:
以下代码在启用 -GR-
和 -EHa-
后会导致编译错误:
#include <iostream>
#include <typeinfo>
class Base { virtual void func() {} };
class Derived : public Base {};
int main() {
try {
throw std::runtime_error("Error"); // 会报错
} catch (...) {
std::cout << "Caught an exception\n";
}
Base* b = new Derived();
if (typeid(*b) == typeid(Derived)) { // 会报错
std::cout << "Derived type\n";
}
return 0;
}
编译器反馈:
- 禁用 RTTI 后,
typeid
和dynamic_cast
的使用会报错。 - 禁用异常后,
try-catch
和throw
的使用会报错。
总结
-GR-
:禁用运行时类型信息,优化性能和减小代码体积。-EHa-
:禁用异常处理支持,进一步减少代码开销,但需要谨慎处理错误情况。- 两者结合适用于不需要运行时类型检查和异常支持的高性能或嵌入式项目。
-EHa-
:我貌似不会报错不知道为啥
# 为 MSVC 编译器添加参数
if(MSVC) # 如果编译器是 MSVC(Microsoft Visual C++)
# 设置 C++ 编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -GR- -EHa- /WX /W4 /wd4819 /wd4201 /Z7 /FC")
# /WX : 将所有警告视为错误(会让编译因警告失败)
# /W4 : 设置警告级别为 4,显示大多数警告
# /wd4819 : 屏蔽警告 C4819,避免文件编码问题导致的警告
endif()
在编译和链接程序时,/link
选项是用于 Microsoft Visual C++(MSVC)编译器 的一个特殊选项,主要用来向链接器传递参数。它通常用于在命令行中,将编译器选项与链接器选项分开。
使用场景
/link
后面跟随的是直接传递给链接器的参数,而不是编译器的选项。通过这种方式,可以在一次命令中同时指定编译器和链接器的设置。
示例
cl main.cpp /link /OUT:myprogram.exe
cl
: MSVC 的编译器工具。main.cpp
: 源代码文件。/link
: 告诉编译器,接下来的参数是链接器选项。/OUT:myprogram.exe
: 链接器选项,用于指定生成的可执行文件名称。
常见的链接器选项
以下是一些可以与 /link
一起使用的链接器选项:
/OUT:<filename>
: 指定输出文件名称。/LIBPATH:<path>
: 指定搜索库的路径。/SUBSYSTEM:<type>
: 指定子系统类型(如CONSOLE
或WINDOWS
)。/DEBUG
: 启用调试信息。/ENTRY:<function>
: 指定程序入口点。
注意事项
- 顺序:
/link
后的参数会直接传递给链接器,因此它必须位于链接器选项之前。 - 用途:
/link
一般在需要自定义链接行为或指定复杂链接器选项时使用。
/MD
和 /MT
都是 Microsoft Visual C++ 编译器的选项,用于指定运行时库的链接方式。它们的区别在于使用的是 动态链接库(DLL)还是 静态链接库(.lib)。这两个选项控制如何处理 C++ 标准库和 C 运行时库的链接。
/MD
- 动态链接运行时库(DLL)
- 解释:使用 动态链接库(DLL)来链接 C 运行时库和 C++ 标准库。
- 行为:编译器会链接到动态运行时库
msvcrt.dll
(Microsoft C Runtime Library)。这意味着你的程序在运行时需要依赖于外部的动态链接库文件。 - 优点:生成的可执行文件较小,因为运行时库被共享,可以由多个程序同时使用。
- 缺点:在程序运行时需要确保目标系统上已经安装了相应版本的 DLL 文件,否则程序将无法启动。
/MT
- 静态链接运行时库(静态库)
- 解释:使用 静态链接库 来链接 C 运行时库和 C++ 标准库。
- 行为:编译器会将运行时库和 C++ 标准库的代码直接嵌入到可执行文件中。这样,你的程序不需要依赖于任何外部动态链接库。
- 优点:生成的可执行文件完全独立,不依赖任何外部的 DLL 文件,这对于某些环境(如嵌入式系统)或需要部署独立程序的场景是有利的。
- 缺点:可执行文件会更大,因为运行时库的所有代码都被静态地链接进去了。此外,不同程序之间无法共享运行时库,因此如果多个程序使用相同的静态库,会导致冗余代码。
/MDd
和 /MTd
(调试版本)
/MDd
:动态链接调试版本的运行时库(DLL),通常用于调试模式。/MTd
:静态链接调试版本的运行时库(静态库),通常用于调试模式。
总结
/MD
:动态链接运行时库,程序运行时依赖msvcrt.dll
,适用于需要共享运行时库的场景。/MT
:静态链接运行时库,程序包含所有运行时库的代码,适用于希望程序完全独立的场景。
选择哪一个取决于项目的需求:
- 如果需要减小程序大小并共享库,使用
/MD
。 - 如果需要完全独立的程序,且不依赖于外部 DLL 文件,使用
/MT
。
-Gm-
是 Microsoft Visual C++ 编译器的一个选项,用于控制 生成程序的最小调试信息。
解释:
-Gm-
禁用最小调试信息的生成。- 默认情况下,编译器会生成较为详细的调试信息,以便调试工具(如 Visual Studio 调试器)能够提供更全面的调试支持。
- 使用
-Gm-
后,编译器会关闭这种调试信息生成,但它不会完全禁用所有调试信息,只是减少生成的调试信息量。
使用场景:
- 减少调试信息大小:在某些情况下,你可能希望生成较小的调试信息文件,以减小程序的尺寸,尤其是在发布版本中。
- 优化编译速度:禁用最小调试信息可能会略微提高编译速度,尤其是在大型项目中。
对比其他调试选项:
-Gm
:启用最小调试信息,生成较少的调试信息,主要用于优化构建过程。-Zi
:生成完整的调试信息,通常用于调试版本的构建。-Z7
:生成最小调试信息,通常适用于调试工具,如在没有完整源代码时进行基本的调试。
总结
-Gm-
选项用于禁用最小调试信息的生成,在某些情况下可以减少调试信息的体积,但通常不推荐用于需要调试的场合。
在使用 -Fm
选项生成 .map
文件时,符号名称(如函数名、变量名)会被编译器名称修饰(名称改编或 mangling)。这是因为 C++ 编译器需要通过对符号名称进行修饰来支持函数重载、模板等特性。
1. C++ 符号名称修饰的原因:
C++ 是一种支持函数重载、模板和命名空间的语言。这意味着同一函数名可能会在不同的上下文中定义多个版本。例如:
- 同名函数的不同参数类型(函数重载)。
- 模板类或函数的实例化(泛型编程)。
为了区分这些函数或变量,编译器会对它们的名称进行修饰(mangling),即将函数名或变量名与其类型信息、模板参数等信息结合起来,形成唯一的标识符。
2. 符号名称修饰的表现:
在 .map
文件中,你会看到原本简单的函数名或变量名被改为一长串看似无意义的字符。这些字符是由编译器根据函数的参数类型、返回类型、命名空间等信息生成的,目的是避免不同的符号冲突。
例如:
- 一个简单的函数
int add(int, int)
在.map
文件中的名称可能会变成_Z3addiij
,其中_Z3add
表示函数名,ii
表示两个int
类型的参数,j
表示返回值类型。
3. 修饰后的符号名称的含义:
_Z
:标志名称是经过修饰的(由 C++ 编译器生成)。- 函数名和类型信息:修饰符中包含函数名以及参数的类型和顺序,甚至返回值的类型。
4. 如何避免名称修饰:
如果你不希望看到这些修饰的符号名称,可以考虑使用 extern "C"
来避免 C++ 的名称修饰。extern "C"
告诉编译器按 C 语言规则进行符号命名,不进行修饰。
例如:
extern "C" {
int add(int a, int b) {
return a + b;
}
}
这种方式下,add
函数在 .map
文件中的名称将是 add
,而不是修饰后的名称。
结论:
在使用 -Fm
选项生成 .map
文件时,符号名称被修饰是因为 C++ 编译器通过名称修饰来支持函数重载、模板、命名空间等特性。如果需要避免这种修饰,可以使用 extern "C"
来禁止名称修饰。
/Od
是 Microsoft Visual C++ 编译器的一个选项,用于禁用优化。
作用:
/Od
表示 禁用所有优化(Disable optimization),即编译器不会进行任何优化操作,包括常见的如代码内联、循环展开、死代码删除等。- 这个选项通常用于调试时,以便让调试信息更准确,程序的行为更接近源代码的编写方式。禁用优化后,编译器不会对代码进行任何修改,便于开发者更容易地跟踪和调试程序的执行流程。
使用场景:
- 调试阶段:在调试程序时,禁用优化可以避免优化所带来的副作用(如变量的值被编译器优化掉、函数调用被内联等),使得调试时的行为和源代码一致。
- 排查问题:在排查程序错误时,禁用优化有助于确认程序的实际行为,因为优化可能会改变代码的执行顺序或变量的生命周期。
示例:
cl /Od myprogram.cpp
这个命令会编译 myprogram.cpp
,并禁用所有优化。
总结:
/Od
选项用于禁用编译器的优化,通常在调试过程中使用,以确保调试信息与源代码更一致。