淘宝联盟推广网站怎么做wap页面是什么意思
C++初学者指南-4.诊断—基础知识:警告和测试
文章目录
- C++初学者指南-4.诊断---基础知识:警告和测试
 - 1. 术语和技术
 - 记住:使用专用类型!
 
- 2.编译器警告
 - Gcc/CLang 编译器选项
 - MS Visual Studio 编译器选项
 
- 3.断言
 - 运行时断言
 - 静态断言(C++11)
 
- 4.测试
 - 指南
 - doctest 简单测试案例
 - doctest 子用例
 - 不要直接使用 cin/cout/cerr!
 
1. 术语和技术
| Warnings 警告 | 编译器消息提示潜在的运行时行为问题的陷阱 | (见下文) | 
| Assertions 断言 | 用于比较和报告表达式的预期值和实际值的语句 | (见下文) | 
| Testing 测试 | 比较程序部分或整体的实际行为和预期行为 | (见下文) | 
| Code Coverage 代码覆盖率 | 实际执行或测试的代码量 | gcov… | 
| Static Analysis 静态分析 | 通过分析源代码,发现潜在的运行时问题,比如未定义的行为 | ASAN UBSAN… | 
| Dynamic Analysis 动态分析 | 通过运行实际程序来发现潜在问题,比如内存泄漏 | valgrind… | 
| Debugging 调试 | 在运行时单步执行代码并检查内存中值 | (接下来) | 
| Profiling 分析 | 找出每个函数/循环/代码块对总运行时间、内存消耗的影响有多大 | |
| Micro Benchmarking 微基准测试 | 衡量单个函数或一组语句/调用运行时间的小测试,而不是整个程序运行 | 
记住:使用专用类型!
- 限制输入参数值
 - 确保中间结果的有效性
 - 保证返回值的有效性
 
目标:编译器作为正确性检查工具-如果编译成功,那就应该是正确的。
// 输入保证:角度以弧度为单位
Square make_rotated (Square const&, Radians angle);
// 输入保证:仅有效数量(例如 > 0)
Gadget duplicate (Gadget const& original,  Quantity times);
//  结果保证:向量已经归一化
UnitVector3d dominant_direction (WindField const&);
// 避免混淆,使用一个好的单位库
si::kg mass (EllipsoidShell const&, si::g_cm3 density);
…
 
2.编译器警告
编译器错误 = 程序不可编译
 编译器警告 = 程序可编译,但有一段有问题的代码可能会导致运行时错误
Gcc/CLang 编译器选项
重要
| -Wall | 强烈推荐。你应该始终至少使用这个。它并不完全启用所有警告,而是启用那些最重要的,不会产生太多误报的警告。 | 
| -Wextra | 启用比 -Wall 还多的警告。强烈推荐 | 
| -Wpedantic | 强烈推荐。需按照严格的ISO C++发出所有警告,拒绝编译器特定的扩展 | 
| -Wshadow | 强烈建议。当变量或类型声明相互遮蔽时发出警告 | 
| -Werror | 将所有警告视为错误⇒任何警告都将终止编译 | 
| -fsanitize=undefined,address | 启用未定义行为检测器和地址检测器(后文将更详细介绍) | 
推荐的编译选项(生产级别)
 -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Werror -fsanitize=undefined,address -Wfloat-equal -Wformat-nonliteral -Wformat-security -Wformat-y2k -Wformat=2 -Wimport -Winvalid-pch -Wlogical-op -Wmissing-declarations -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-noreturn -Wnested-externs -Wpacked -Wpointer-arith -Wredundant-decls -Wstack-protector -Wstrict-null-sentinel -Wswitch-enum -Wundef -Wwrite-strings
高性能/低内存/安全
 注:这里的吵和噪音是指有很多警告的意思
 可能会很吵!
 -Wdisabled-optimization -Wpadded -Wsign-conversion -Wsign-promo -Wstrict-aliasing=2 -Wstrict-overflow=5 -Wunused -Wunused-parameter
MS Visual Studio 编译器选项
| /W1 | 1 级:严重警告 | 
| /W2 | 2 级:重大警告 | 
| /W3 | 3 级:生产级别警告。 您应该始终至少使用这个。 也是较新的 Visual Studio 项目的默认值。 | 
| /W4 | 强烈推荐,特别是对于新项目。 并没有真正启用所有警告,而是启用最多的警告 重要的不会产生太多误报噪音。 | 
| /Wall | 启用比 4 级更多的警告;可能有点太吵了 | 
| /WX | 将所有警告视为错误⇒任何警告都会终止编译 | 
3.断言
运行时断言
#include <cassert>
assert(bool_expression);
 
如果表达式 产生 false,则中止程序
 用例:
- 在运行时检查预期值/条件
 - 验证先决条件(输入值)
 - 验证不变量(例如,中间状态/结果)
 - 验证后置条件(输出/返回值)
 
应在发布版本中停用运行时断言 以避免任何性能影响。
#include <cassert>
double sqrt (double x) {assert( x >= 0 );…
}
double r = sqrt(-2.3);
 
$ g++ … -o runtest test.cpp
$ ./runtest
runtest: test.cpp:3: void sqrt(double): Assertion `x >= 0' failed.
Aborted
 
逗号必须用括号保护
 assert 是一个预处理器宏(稍后会详细介绍) 否则逗号将被解释为宏参数分隔符:
assert( min(1,2) == 1 );  //  ERROR
assert((min(1,2) == 1));  //  OK
 
断言信息
 可以使用自定义宏添加(没有标准方法):
#define assertmsg(expr, msg) assert(((void)msg, expr))
assertmsg(1+2==2, "1 plus 1 must be 2");
 
(不)激活 – g++/clang
 通过定义预处理器宏 NDEBUG 来停用断言, 例如,使用编译器开关:
g++ -DNDEBUG …
 
(不)激活 – MS Visual Studio
 断言已被明确激活
- 如果预处理宏 _DEBUG 被定义,例如,通过编译器开关 /D_DEBUG
 - 如果提供了编译器开关/MDd
 
如果在项目设置中或使用编译器开关/DNDEBUG定义了预处理宏NDEBUG,那么断言会被明确地禁用。
静态断言(C++11)
static_assert(bool_constexpr, "message");
static_assert(bool_constexpr);  (C++17)
 
如果编译时常量表达式产生 false,则中止编译。
…
using index_t = int;
index_t constexpr DIMS = 1;  // oops
void foo () { static_assert(DIMS > 1, "DIMS must be at least 2");…
}
index_t bar (…) {static_assert(std::numeric_limits<index_t>::is_integer &&std::numeric_limits<index_t>::is_signed, "index type must be a signed integer");…
}
 
$ g++ … test.cpp
test.cpp: In function 'void foo()':
test.cpp:87:19: error: static assertion failed: DIMS must be at least 287 |  static_assert(DIMS > 1, "DIMS must be at least 2");|                ~~^~~
 
4.测试
指南
使用断言
 检查类型无法表达或保证的期望和假设:
- 仅在运行时可用的期望值
 - 前提条件(输入值)
 - 不变量(例如,中间状态/结果)
 - 后置条件(输出/返回值)
 
在发布版本中应该关闭运行时断言,以避免任何性能影响。
编写测试
 一旦确定了函数或类型的基本用途和接口。
- 更快的开发:减少耗时的日志记录和调试会话需求。
 - 更容易的性能调优:可以持续检查是否仍然正确。
 - 文档:期望/假设被写在代码中。
 
使用测试框架
 更方便,更少出错:预定义检查、设置设施、测试运行器等。
 初学者 / 较小的项目: doctest
- 非常简洁和自我说明的风格
 - 轻松设置:只需包含一个标题
 - 非常快的编译
 
大型项目:Catch2
- 与doctest相同的基本理念(doctest是模仿Catch设计的)。
 - 用不同数值执行相同测试的数值生成器。
 - 使用计时器进行微基准测试,取平均值等。
 - 编译速度较慢,设置起来比doctest稍微复杂一些。
 
doctest 简单测试案例
// 摘自文档测试教程:
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
int factorial (int n) {if (n <= 1) return n;return factorial(n-1) * n;
}
TEST_CASE("testing factorial") {CHECK(factorial(0) == 1);CHECK(factorial(1) == 1);CHECK(factorial(2) == 2);CHECK(factorial(3) == 6);CHECK(factorial(10) == 3628800);
}
 
$ g++ … -o runtest test.cpp
$ ./runtest
test.cpp(7) FAILED!
CHECK( factorial(0) == 1 )
with expansion:
CHECK( 0 == 1 )
 
测试失败,因为factorial的实现无法正确处理 n = 0 的情况。
 运行此示例
doctest 子用例
// 摘自文档测试教程:
TEST_CASE("vectors can be sized and resized") {std::vector<int> v(5);REQUIRE(v.size() == 5);REQUIRE(v.capacity() >= 5);SUBCASE("push_back increases the size") {v.push_back(1);CHECK(v.size() == 6);CHECK(v.capacity() >= 6);}SUBCASE("reserve increases the capacity") {v.reserve(6);CHECK(v.size() == 5);CHECK(v.capacity() >= 6);}
}
 
对于每个子用例,测试用例都是从头开始执行的。
 当执行每个子用例时,我们知道vector大小为 5,容量至少为 5。 我们在顶层通过 REQUIRE 强制执行这些要求。
- 如果 CHECK 失败:测试被标记为失败,但执行会继续进行。
 - 如果 REQUIRE 失败:执行停止。
运行此示例 
不要直接使用 cin/cout/cerr!
直接使用全局 I/O 流使得函数或类型难以测试:
void bad_log (State const& s) { std::cout << … }
 
在函数中:通过引用传递流
struct State { std::string msg; … };void log (std::ostream& os, State const& s) { os << s.msg; }TEST_CASE("State Log") {State s {"expected"};std::ostringstream oss;log(oss, s);CHECK(oss.str() == "expected");
}
 
运行上面示例
类范围:存储流指针
 但是:请尝试编写与流或任何其他特定I/O方法无关的类型。
class Logger {std::ostream* os_;int count_;
public:explicitLogger (std::ostream* os): os_{os}, count_{0} {}…bool add (std::string_view msg) {if (!os_) return false;*os_ << count_ <<": "<< msg << '\n';++count_;return true;}
};TEST_CASE("Logging") {std::ostringstream oss;Logger log {&oss};log.add("message");CHECK(oss.str() == "0: message\n");
}
 
运行上面示例
附上原文链接
 如果文章对您有用,请随手点个赞,谢谢!^_^

