【C++编程调试秘籍】| 总结归纳要点(上)

news2024/11/19 2:44:57

文章目录

    • 一、编译器是捕捉缺陷的最好场合
      • 1 如何使用编译器捕捉缺陷
    • 二、在运行时遇见错误该如何处理
      • 1 该输出哪些错误信息
      • 2 执行安全检查则会减低程序效率,该如何处理呢
      • 3 当运行时遇到错误时,该如何处理
    • 四、索引越界
      • 1 动态数组
      • 2 静态数组
      • 3 多维数组
    • 5 指针运算
    • 6 无效的指针、引用和迭代器
    • 七、未初始化的变量
      • 1 初始化的数值
      • 2 未初始化的布尔值
    • 八、内存泄漏
      • 1 引用计数指针
      • 2 作用域指针
      • 3 用智能指针实行所有权
    • 九、解引用NULL指针
    • 十、拷贝构造函数和赋值操作符
    • 十一、避免在析构函数中编写代码

一、编译器是捕捉缺陷的最好场合

原因

  • 可以准确的看到缺陷时发生在哪一行、哪一个文件、以及错误的描述;
  • 节省时间;

1 如何使用编译器捕捉缺陷

  • 可以新增一项安全检查,确保传递给函数的值必须位于指定区域内;

新增安全检查

  • 禁止隐式类型转换,用explicit声明一个接收1个参数的构造函数,并避免使用转换操作符;
  • 使用不同的类来表示不同的数据类型(后续提到);
  • 用typedef来重命名该类型;
    在这里插入图片描述

二、在运行时遇见错误该如何处理

如:打开一个文件不存在或没有权限,或访问链接不可用等,为运行时错误;
为此,我们需要输出错误信息:输出错误的原因,错误的地方,相关的变量值,并采取适当的操作

1 该输出哪些错误信息

使用该宏,当条件为时即可触发,将错误信息输出;

#define ASSERT(condition, msg)	\
	if(!(condition)) {			\
		std::cerr << "in file: "__FILE__; \
		std::cerr << " #" << __LINE__ << ": "; 	\
		std::cerr << msg;				\
		exit(1);\
	}

int main() {
	ASSERT(0 > 1, "test");
}
// 示例:
/**
ASSERT(index < array.size(), "Index " << index << " is out of bounds " << array.size());
输出足够的信息,让该错误信息更有意义;
*/

2 执行安全检查则会减低程序效率,该如何处理呢

  • 我们可以将安全检查分为生产使用和测试使用,当不需要时,则可以将其关闭,从而不降低程序的执行效率;
  • 那么何时使用生产、何时使用测试,需要开发者来决定,需要考虑该处使用安全检查其调用频率执行时间(条件的执行时间)等;
  • 且在初次编写代码时,就需要一同编写安全检查,而不是过后在做补充;
#define TEST_ASSERT_ON	// 若定义就开启

#ifdef TEST_ASSERT_ON
#define TEST_ASSERT(condition, msg) ASSERT(condition, msg)
#else
#define TEST_ASSERT(condition, msg)
#endif

#define ASSERT(condition, msg)	\
	if(!(condition)) {			\
		std::cerr << "in file: "__FILE__; \
		std::cerr << " #" << __LINE__ << ": "; 	\
		std::cerr << msg;				\
		exit(1);\
}

int main() {
	TEST_ASSERT(0 > 1, "test");
}

3 当运行时遇到错误时,该如何处理

  • 终止程序:一般用于测试时,立即中断程序,修改后继续尝试;
  • 抛出异常:在程序实际工作中,不宜将程序直接终止,而是抛出一个异常,在将具体的错误信息交给错误处理函数,并在顶层代码中捕捉该异常,将其记录在日志文件中,不会干扰到其他功能的正常运行;
// 一
#ifdef THROW_EXCEPTION_ON_BUG
	throw std::logic_error("error");
#else
	std::cout << "exit" << std::endl;
	exit(1);
#endif

// 二
int main() {
	try {
		double stock_price{ 100.0 };
		ASSERT(
			0 < stock_price && stock_price <= 1e6,
			"Stock price " << stock_price << " is out of range.");

		stock_price = -1.;
		ASSERT(
			0 < stock_price && stock_price <= 1e6,
			"Stock price " << stock_price << " is out of range.");
	}
	catch (const std::exception& ex) {
		std::cerr << "Exception caught in " << __FILE__
			<< " #" << __LINE__ << ":\n"
			<< ex.what() << std::endl;
	}

	return 0;
}

四、索引越界

  • 不要使用静态动态分配的数组,改用arrayvector模板;
  • 不要使用带[]的new和delete操作符,让vector模板为多个元素分配内存;
  • 继承vector,对需要增加安全检查的进行重写;
  • 对于多维数组,通过()访问元素,并提供越界检查;

1 动态数组

若出现越界的情况,则会返回错误的信息;但对于vector提供了at使用,该方法能够抛出一个out_of_range异常,但会降低代码的效率;
故,我们可以自己捕捉目标改写

namespace scpp {

template <typename T>
class vector : public std::vector<T> {
    public:
        typedef unsigned size_type;    

        explicit vector(size_type n = 0)
            : std::vector<T>(n) { }

        vector(size_type n, const T& value) 
            : std::vector<T>(n, value) { }

        template <class InputIterator> vector(InputIterator first,
                                              InputIterator last)
            : std::vector<T>(first, last) { }

        T& operator[] (size_type index) {
            std::stringstream stringStream;
            SCPP_TEST_ASSERT(index < std::vector<T>::size(),
                             stringStream << "Index " << index 
                             << " must be less than "
                             << std::vector<T>::size());
            return std::vector<T>::operator[](index);
        }

        const T& operator[] (size_type index) const {
            std::stringstream stringStream;
            SCPP_TEST_ASSERT(index < std::vector<T>::size(),
                             stringStream << "Index " << index
                             << " must be less than "
                             << std::vector<T>::size());
            return std::vector<T>::operator[](index);
        }
};
}

vector注意事项

  • 当不断向vector插入数据后,vector插入数据可能变得缓慢,故我们需要预先给vector分配内存;
vector<int> vec(n, 0);	// 具有初始化的内存分配
vector<int> vec;		// 无初始化的内存分配
vec.reserve(n);

从vector中派生新类的注意事项
若基类的析构函数不为虚函数,则在多态使用时则子类的析构函数不会被执行到;

  • 故不对子类中添加任何数据成员;
  • 不使用多态的特性;
class Base{
public:
	~Base();	// 非虚拟
};

class Derived : public Base {
public:
	~Derived() {}	// 非虚拟
};

Base* p = new Derived;
delete p;	// 此时delete调用的是基类的析构,不会调用子类的析构

2 静态数组

  • 静态数组从堆栈上分配内存,而vector是通过new分配,速度较慢些,但最好使用array模板(静态数组);
  • 建议不要使用静态、动态数组而是使用vector或array模板;

3 多维数组

  • 多维一般不使用vector的vector,它需要多次分配内存,是低效的;
  • 采用一维度的数组进行存储,在索引上做处理,通过()对元素进行访问,由于[]操作符只能接受1个参数;
template<typename T>
class matrix {
public:
	typedef unsigned size_type;

	matrix(size_type rows, size_type cols)
		:m_rows(rows), m_cols(cols) {
		ASSERT(m_rows > 0, "rows must be positive");
		ASSERT(m_rows > 0, "cols must be positive");
	}

	matrix(size_type rows, size_type cols, const T& init_val)
		:m_rows(rows), m_cols(cols), m_data(m_row*m_cols, init_val) {
		ASSERT(m_rows > 0, "rows must be positive");
		ASSERT(m_rows > 0, "cols must be positive");
	}

	size_type getRows() const { return m_rows; }
	size_type getCols() const { return m_cols; }

	T& operator() (size_type row, size_type col) {
		return m_data[index(row, col)];
	}

	const T& operator() (size_type row, size_type col) const {
		return m_data[index(row, col)];
	}

private:
	size_type index(size_type row, size_type col) const {
		ASSERT(row < m_rows, "Row " << row << " must be less than " << m_rows);
		ASSERT(col < m_cols, "Col " << col << " must be less than " << m_cols);
		return col * row + col;
	}

private:
	size_type m_rows;
	size_type m_cols;
	std::vector<T> m_datas;
};

5 指针运算

  • 避免使用指针运算,使用vector模板或数组索引;

6 无效的指针、引用和迭代器

#include <iostream>
#include <vector>

int main() {
	std::vector<int> v;
	for (int i = 0; i < 10; ++i) v.push_back(i);

	int* old_vptr = &v[3];
	int& old_ref = v[3];
	std::vector<int>::const_iterator old_iter = v.begin() + 3;

	std::cout << "vptr: " << *old_vptr << " ref: " << old_ref <<  " addr: " << old_vptr << std::endl;

	std::cout << "add elements..." << std::endl;

	for (int i = 0; i < 100; ++i) v.push_back(i*10);

	std::vector<int>::const_iterator new_iter = v.begin() + 3;
	std::cout << "vptr: " << *old_vptr << " ref: " << old_ref << " addr: " << &v[3] << std::endl;

	return 0;
}

在这里插入图片描述

  • 出现上述原因是由于&v[3]的地址发生变化,当vector添加元素超出当前容量时,会重新分配一块内存将当前所有值都移植过去,旧的内存将被销毁;
  • 引用也与指针相同,引用只是解引用的指针;
  • 故任何指针、引用或迭代器在修改后都不应被再使用,即使有些容器其迭代器仍有效;

七、未初始化的变量

  • 对于类的数据成员,不要使用内置类型;
  • 重新定义该类型,还可获取编译时类型安全的优点;

1 初始化的数值

  • 在类中,当添加一个数据成员时,即需要在构造函数中对它进行初始化,若有多个构造函数,而没添加数据成员都需要做修改,则需要做大量的工作;
  • 该初始化只针对内置类型,由于内置类型不会有默认的构造函数将它初始化,而类型string类型,则会被默认初始化;
template<typename T>
class TNumber {
public:
	TNumber(const T& x = 0) : m_data(x) {}

	operator T() const { return m_data; }

	TNumber& operator=(const T& x) {
		m_data = x;
		return *this;
	}

	TNumber operator++(int) {
		TNumber<T> copy(*this);
		++m_data;
		return *this;
	}

	TNumber& operator++() {
		++m_data;
		return *this;
	}

	TNumber& operator+=(T x) {
		m_data += x;
		return *this;
	}

	TNumber& operator-=(T x) {
		m_data -= x;
		return *this;
	}

	TNumber& operator*=(T x) {
		m_data *= x;
		return *this;
	}

	TNumber& operator/=(T x) {
		ASSERT(x!=0, "Attmpt to divide by 0");
		m_data /= x;
		return *this;
	}

	T operator/(T x) {
		ASSERT(x != 0, "Attmpt to divide by 0");
		return m_data / x;
	}
private:
	T m_data;
};

typedef TNumber<int> Int;
...


class A {
public:
	Int m_a;	// 此时即可被初始化
};

2 未初始化的布尔值

class Bool {
public:
	Bool(bool x = false)
		:m_data(x) {}

	operator bool() const { return m_data; }

	Bool& operator=(bool x) {
		m_data = x;
		return *this;
	}

	Bool& operator&=(bool x) {
		m_data &= x;
		return *this;
	}

	Bool& operator |= (bool x) {
		m_data |= x;
		return *this;
	}

private:
	bool m_data;
};


std::ostream& operator<<(std::ostream& os, Bool val) {
	if (val) {
		os << "True";
	}
	else {
		os << "False";
	}

	return os;
}

八、内存泄漏

》》》》》》》》内存泄漏参考链接《《《《《《《《

  • 当分配到新内存是,需要立即把指向该内存的指针赋值给智能指针;

内存泄漏:实际是被分配的内存的所有权丢失;

对象的所有权:删除该内存的责任;

可能出现内存泄漏的原因

  • 分配内存后,没有释放即离开当前作用域;
  • 当前内存地址赋给了其他值;
  • 程序运行时,一直保留不在使用的指针;
  • 循环引用导致得不到释放;
  • 对指针赋值NULL,丢失该对象;

注意事项

  • 使用完毕需要删除这块内存;
  • 释放任务只执行一次;
  • delete时,需要注意是否有带[];

1 引用计数指针

该类型的指针销毁时刻为最后消亡的那个对它进行删除;

》》》》》》》》智能指针参考链接《《《《《《《《
该类型指针存在的问题

  • 不是多线程安全;
  • 内存会在堆上new一个整数,速度相对慢;

2 作用域指针

  • 该指针用于不需要复制指针,只想保证被分配的资源被正确的回收;
  • 该指针没有赋值和拷贝方法,不需要再堆上分配整数对它进行拷贝计数;

3 用智能指针实行所有权

当使用一个函数来获取一个指针时,可能会出现的潜在错误,避免对象的所有权不明确等原因,让函数返回一个智能指针;

std::shared_ptr<MyClass> MyFactoryClass::Create(const Input& inputs);
// or
void MyFactoryClass::Create(const Inputs& inputs, auto_ptr<MyClass>& result);

九、解引用NULL指针

当我们试图解引用NULL指针时,将会导致程序崩溃,为此以下提供一种不删除它所指向对象的半智能指针;

template<typename T>
class Ptr {
public:
	explicit Ptr(T* p = nullptr)
		: m_ptr(p) {}

	T* Get() const { return m_ptr; }

	Ptr<T>& operator=(T* p) {
		m_ptr = p;
		return *this;
	}

	T* operator->() const {
		ASSERT(m_ptr != nullptr, "Attempt to use operator -> on NULL pointer.");
	
		return m_ptr;
	}

	T& operator*() const {
		ASSERT(m_ptr != nullptr, "Attempt to use operator -> on NULL pointer.");
		
		return *m_ptr;
	}

private:
	T* m_ptr;
};

上述指针的特性

  • 它不拥有它所指向的对象的所有权,是相同情况下原始指针的替代品;
  • 它默认被初始化为NULL;
  • 提供了运行时诊断,当它为NULL时,若进行解引用,则可对该行为进行检测;

十、拷贝构造函数和赋值操作符

  • 依赖编译器自动创建的默认版本;
  • 将拷贝构造函数和赋值操作符声明为私有,不提供实现禁止任何复制;
  • 尽量避免编写自己的版本;

十一、避免在析构函数中编写代码

  • 在设计类的时候,尽量将析构函数保持为空函数;

何时需要在析构中编写

  • 在基类中,可能需要声明虚拟析构函数;
  • 需要对数据成员进行一个释放;

比较有实质性的做法可能就是在析构中对类中资源的释放,但该做法可能交给智能指针去处理并不需要交给我们手动处理;

交给智能指针的好处

  • 若在构造中new一块内存时,需要new第二块内存,此处出现错误,若使用智能指针,则内存都会被释放掉;
  • 不需要在类的析构中对它进行清理;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/135767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【回答问题】ChatGPT上线了!给我推荐20个比较流行的深度学习模型

目录给我推荐20个比较流行的nlp模型给我推荐20个比较流行的计算机视觉模型给我推荐20个比较流行的图像分类模型给我推荐20个比较流行的人脸识别模型给我推荐20个比较流行的实体识别模型给我推荐20个比较流行的语言识别模型给我推荐20个比较流行的激光雷达3D点云模型给我推荐20个…

计算机网络的一些常识

序 小白&#xff0c;啥也不会&#xff0c;所以要学习常识 快速总览 这个视频按照数据链路层——网络层——传输层——应用层的顺序&#xff0c;自下向上介绍的。虽然只有30分钟&#xff0c;但是挺全的&#xff0c;密度挺高的&#xff0c;而且小白友好。 计算机网络 Compute…

python学习|第一天

文章目录1.输出函数print2.浮点数输出3.数据类型转换4.运算符优先级5.列表对象list1.输出函数print #输出数字&#xff0c;直接输出 print(2023) print(2023.1)#输出字符串&#xff0c;要加单引号或者双引号(实际效果都是一样的)&#xff0c;输出后自动换行 print(hello 2023)…

华为CE系列和S系列交换机堆叠配置及mad检测

CE系列交换机堆叠配置&#xff1a; 第一台交换机配置&#xff1a; system-view immediately #不用每次都输入 commit提交了 sysname sw1 stack stack member 1 priority 105 stack member 1 domain 10 quit inter stack-port 1/1 port member-group inter 10GE 1/0/1 dis st…

使用Helm部署Wikijs

使用 Helm 部署 Wiki.js &#x1f4da;️ 参考文档: Wiki.js 官方文档 - 安装 - Kubernetes Wiki.js 使用 Helm 安装 Wiki.js 官方文档 - 安装 - 侧加载 官方教程 Kubernetes 开始使用 Helm Chart 在 Kubernetes 上安装 先决条件 Kubernetes 集群HelmPostgreSQL 数据库 ❗…

实习------数据库进阶

B树索引 什么是索引&#xff1f; MySQL官方对索引的定义为&#xff1a;索引就是用于实现数据的快速检索&#xff0c;由数据表中的一列或多列组合而成&#xff0c;索引实质上是一张描述索引列的列值与原表中记录行之间一 一对应关系的有序表。索引的实现通常使用B树及其变种B树…

2022年终回顾与总结:螃蟹走路-冲,撞

工作赚钱&#xff0c;养家糊口 << 2022年对地球上的人类来说&#xff0c;肯定是刻骨铭心的纪元。对于微小的个人而言&#xff0c;感受是真真切切的。固然全球疫情危害了劳苦大众&#xff0c;但家庭给我的触动却是直接和深刻的。 这一年的轨迹被6月的一刀切成两片。上半年…

深度学习——序列模型(笔记)

1.序列数据&#xff1a; ①现实生活中有很多数据是有时序结构&#xff0c;比如电影的评分随时间的变化而变化。 ②统计学中&#xff0c;超出已知观测范围进行预测是外推法&#xff0c;在现有的观测值之间进行估计是内插法 2.统计工具&#xff1a;处理序列数据选用统计工具和新…

第一章:Mybatis与微服务注册

目录 一、SpringBoot整合MybatisPlus 创建自动生成代码子模块 创建商品服务子模块 二、SpringBoot整合Freeamarker 三、SpringBoot整合微服务&gateway&nginx 整合微服务之商品服务zmall-product 创建并配置网关gateway服务 安装配置SwitchHosts 安装配置Windo…

安装包部署prometheus+Grafana+node_exporter

部署prometheus 在192.168.11.141服务器操作 下载prometheus安装包 wget https://github.com/prometheus/prometheus/releases/download/v2.32.1/prometheus-2.32.1.linux-amd64.tar.gz 下载prometheus安装包 tar xvf prometheus-2.32.1.linux-amd64.tar.gz -C /usr/local…

OSPF的工作原理与性能优化

OSPF的3张表 OSPF的工作过程分为3个大步骤&#xff0c;分别是形成邻居关系&#xff0c;形成邻接关系&#xff0c;计算路由 OSPF建立邻居&#xff0c;收集LSA&#xff0c;收集完成形成邻接 用收集到的LSA&#xff0c;作为原材料&#xff0c;计算路由 完成这3大步骤&#xff0c;…

分享106个PHP源码,总有一款适合您

源码下载链接&#xff1a;https://pan.baidu.com/s/1Dyc3Qj8JRHJr2sECdEqGrA?pwdlscj 提取码&#xff1a;lscj PHP源码 分享106个PHP源码&#xff0c;总有一款适合您 采集参数 page_count 1 # 每个栏目开始业务content"text/html; charsetgb2312"base_url &q…

MySQL事务隔离级别详解

一、什么是事务 事务&#xff08;Transaction&#xff09;是由一系列对数据库中的数据进行访问与更新的操作所组成的一个程序执行单元。 在同一个事务中所进行的操作&#xff0c;要么都成功&#xff0c;要么就都失败。理想中的事务必须满足四大特性&#xff0c;这就是大名鼎鼎…

8种专坑同事的 SQL 写法,性能降低100倍,不来看看?

今天给大家分享几个SQL常见的“坏毛病”及优化技巧。 SQL语句的执行顺序&#xff1a; 1、LIMIT 语句 分页查询是最常用的场景之一&#xff0c;但也通常也是最容易出问题的地方。比如对于下面简单的语句&#xff0c;一般 DBA 想到的办法是在 type、 name、 create_time 字段上…

第二章:Swagger2

目录 背景介绍 什么是Swagger2 常用注解 SpringBoot整合Swagger2 生产环境下屏蔽Swagger2 修改Swagger2配置类 修改application.yml 使用maven package打包测试 运行测试 背景介绍 在团队开发中&#xff0c;一个好的 API 文档不但可以减少大量的沟通成本&#xff0c;还…

Linux系统的进程管理

文章目录Linux系统的进程管理1.查看进程2.父进程3.终止进程4.进程树Linux系统的进程管理 在LINUX中&#xff0c;每个执行的程序都称为一个进程。每一个进程都分配一个ID号(pid,进程号) 每个进程都可能以两种方式存在的。前台与后台&#xff0c;所谓前台进程就是用户目前的屏幕…

Vulnhub 靶场 Earth

通关方案&#xff1a;https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html 思路流程&#xff1a; 1. 信息收集 nmap扫描发现开了22端口和两个web端口&#xff08;80和443&#xff09;。 注意这里信息收集到到位&#xff0c;获取的信息多一些。 使用nmap默认脚…

常见的降维技术比较:能否在不丢失信息的情况下降低数据维度

本文将比较各种降维技术在机器学习任务中对表格数据的有效性。我们将降维方法应用于数据集&#xff0c;并通过回归和分类分析评估其有效性。我们将降维方法应用于从与不同领域相关的 UCI 中获取的各种数据集。总共选择了 15 个数据集&#xff0c;其中 7 个将用于回归&#xff0…

电子招标采购系统源码—互联网+招标采购

​ ​ 智慧寻源 多策略、多场景寻源&#xff0c;多种看板让寻源过程全程可监控&#xff0c;根据不同采购场景&#xff0c;采取不同寻源策略&#xff0c; 实现采购寻源线上化管控&#xff1b;同时支持公域和私域寻源。 询价比价 全程线上询比价&#xff0c;信息公开透明&#x…

Kong动态负载均衡与服务发现

Kong动态负载均衡一、背景二、通过docker 安装 Kong三、分布式API网关存在的意义四、Kong 的相关特性五、Kong 体系结构六、Kong 工作流程七、从 nginx 配置到 Kong 配置7.1、Kong 核心四对象7.2、四对象关系八、插件机制九、Kong 网关插件十、使用konga10.1、实现一个负载均衡…