C++初学者指南-4.诊断---基础:警告和测试

news/2024/7/8 1:34:12 标签: c++, 开发语言

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 编译器选项

/W11 级:严重警告
/W22 级:重大警告
/W33 级:生产级别警告。 您应该始终至少使用这个。 也是较新的 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 2
 87 |  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:
  explicit
  Logger (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");
}

运行上面示例

附上原文链接
如果文章对您有用,请随手点个赞,谢谢!^_^

在这里插入图片描述


http://www.niftyadmin.cn/n/5535984.html

相关文章

Build a Large Language Model (From Scratch)附录C(gpt-4o翻译版)

来源&#xff1a;https://github.com/rasbt/LLMs-from-scratch?tabreadme-ov-file https://www.manning.com/books/build-a-large-language-model-from-scratch

从GPT-1到GPT-3 预训练语言模型的演进与突破

本文由 ChatMoney团队出品 前言 Generative Pre-trained Transformer&#xff08;GPT&#xff09;系列是由OpenAI开发的预训练语言模型&#xff0c;它们在多种NLP任务中取得了令人瞩目的成绩&#xff0c;包括文章生成、代码生成、机器翻译和问答等。GPT系列模型的核心思想是通…

贪心 | Java | LeetCode 455, 376, 53 做题总结

贪心算法介绍 贪心算法&#xff1a;贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 说实话贪心算法并没有固定的套路。 一般解题步骤 贪心算法一般分为如下四步&#xff1a; ① 将问题分解为若干个子问题 ② 找出适合的贪心策略 ③ 求解每一个子问题的…

推荐一款Win11主题WPF UI框架

最近在微软商店&#xff0c;官方上架了新款Win11风格的WPF版UI框架【WPF Gallery Preview 1.0.0.0】,这款应用引入了前沿的Fluent Design UI设计&#xff0c;为用户带来全新的视觉体验。 WPF Gallery简介 做为一关注前沿资讯的开发人员&#xff0c;首先关注的是应用WPF Gallery…

7.Android逆向协议-抓取安卓http和https数据包(设备需要root权限)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;微尘网校 上一个内容&#xff1a;6.Android逆向协议-配置FD抓包环境 root&#xff1a; 现在的安卓手机不好搞&#xff0c;有很多坑&am…

【chtagpt】pytorch中的方法对象和属性

文章目录 定义一个简单的类属性和方法对象的区别PyTorch 张量中的属性和方法对象进一步解释总结self.value value 的解释示例解释总结 为了更好地理解方法对象和属性&#xff0c;我们可以通过一个简单的类来演示这两者的区别及其用法。 定义一个简单的类 我们定义一个名为 My…

流批一体计算引擎-11-[Flink]实战使用DataStream对接kafka

1 消费kafka[DataStreamAPI] 参考官网DataStream API 教程 参考官网DataStream中的Apache Kafka 连接器 flink 1.14版本及以前,不支持python flink 1.15版本为FlinkKafkaConsumer和FlinkKafkaProducer flink 1.16版本及以后为KafkaSource和KafkaSink pip install apache-flin…

[C++][CMake][生成可执行文件][上]详细讲解

目录 0.准备工作1.添加CMakeLists.txt文件2.执行cmake命令3.变量定义4.指定使用的C标准5.指定输出路径 0.准备工作 add.c#include <stdio.h> #include "head.h"int add(int a, int b) {return ab; }sub.c#include <stdio.h> #include "head.h"…