一、参考资料:

本文主要参考《C++编程调试秘笈》一书。

在编写C++代码时,我们不应该自己去捕捉缺陷,而是让编译器和可执行代码为我们做这些事情。 本书提供了这样一种思路。 作者以“debugger-friendly”的方式,编写了一些方便安全检查所需的宏代码,并对C++代码中最常见的错误制定了一些规则,并在代码中实现,使其在运行时易于捕获。 或者尽可能在编译时捕获错误。

2. C++缺陷的来源

在C语言中,为了追求简洁和速度,生成高效的编译代码,有时没有考虑到一些用户友好的特性,就会导致一些明显的问题,比如垃圾回收、越界检查、缓冲区溢出、 ETC。

程序员可以创建一定长度的数组,并访问索引值超出数组边界的元素

最被滥用的是指针运算。 程序员可以访问由指针算法生成的任何值作为内存地址,而不管内存是否有效或可访问。 例如,取消引用 NULL 指针 strlen(NULL) 将导致程序崩溃。

程序员在运行时使用calloc()和malloc()函数动态分配内存,使用free()函数处理释放内存。但是如果忘记销毁,就会发生内存泄漏(分配后内存没有释放,最终会消耗掉系统空间),或者你不小心多次销毁,导致内存挂起(释放对象后指针没有置为NULL再解引用,未定义指针解引用很严重)等灾难性问题

sprintf() 和一些字符串函数在写入缓冲区时可能会覆盖超出缓冲区末尾的内存,从而导致不可预测的程序行为; 与在缓冲区末尾悄悄截断的相应安全版本相比,但这可能不是我们预期的结果。 推荐多使用C++的string和stringstream

(关于C的string函数和C++的string、stringstream的优点还有争论,有空可以分析一下)

c++ 判断是否为指数_c++判断字符串是否为空_c 判断对象是否为null

当然,C++语言也存在一些问题

朋友和多重继承不是一个好主意

new和delete混用,一种带方括号,一种不带方括号,

请务必使用正确的形式:

A* p_object=new A();
A
* p_array=new A[size];
delete p_object;
delete []p_array;

看完这本书,感触颇深。 比如C++早期,主要侧重于面向对象特性的设计。 后来模板、异常处理、命名空间相继推出。 现在C++11引入了类型推导、lambda函数、标准库的变化(无序哈希表、正则表达式、线程支持等),体验是:

您觉得需要进一步了解的主题:

3. 何时捕捉陷阱

要在编译时诊断错误c++判断字符串是否为空,请遵循以下规则:

c++ 判断是否为指数_c++判断字符串是否为空_c 判断对象是否为null

禁止隐式类型转换:关键字explicit声明一个接受一个参数的构造函数,禁止使用转换运算符

使用不同的类来表示不同的数据类型

不要使用纯功能枚举来创建整数常量,而是使用它们来创建新类型

原因如下:

A. 假设我们有两个类 A 和 B,并且有一个函数需要 B 类型的参数:

void doSomething(const B& b)

但是我们不小心给它提供了一个 A 类型的对象:

A a(input);
doSomething(a);

在某些情况下,这样的代码可以编译,因为它可以执行隐式类型转换:A 和平地转换为 B。它可以通过两种方式发生

1、B类接受一个类型为A的参数构造函数,可以隐式地将A转换为B

class B {
public:
B(
const A& a);
}

2.A类有一个可以将其转换为B的运算符,以显式方式提供转换方法

class A{
public
//转换操作符operator type():type可以是基本数据类型,类,结构体
operator B() const;
}

因此,鉴于以上问题,建议所有接受一个参数的构造函数都用关键字explicit声明,不建议转换运算符。

一般来说,隐式转换的所有可能性都是坏主意。 还记得《计算机系统深度》第二章FreeBSD开源系统中出现的getpeername安全漏洞吗? 这是由于无符号数和有符号数之间的不匹配导致了隐式类型转换。但是我们也可以使用另一种方法来转换

class A{
public
B asB()
const;
}

A a(input);
doSomething(a.asB());
// 显式转换

B、定义两个枚举,分别代表星期几和月份。 这些常量都是整数。假设我们有一个函数需要星期几作为参数

enum {SUN1,MON=1,TUE,WED,THU,FRI,SAT};
enum {JAN=1,FEB,...,DEC};

void func(int day_of_week);

因此下面的调用将在没有任何警告的情况下编译:func(JAN);

所以捕捉这种缺陷的方法就是创建一个新的枚举类型,直接限制新的枚举类型的范围,这样就可以在编译时判断是否有错误。

typedef enum {SUN1,MON=1,TUE,WED,THU,FRI,SAT} DayofWeek;
typedef
enum {JAN=1,FEB,...,DEC} Month;

4.如何处理运行时遇到的错误

我们将精力集中在一类运行时错误——缺陷上。专门为捕获缺陷而编写的一段代码称为安全检查。 当它失败时,就意味着发现了缺陷。 如何处理? 这里,作者提供了这样一个思路

定义 SCPP_ASSERT 宏,这是一种永久性安全检查,可捕获运行时错误并提供有关错误的特定信息

#scpp_assert.h
#define SCPP_ASSERT(condition,msg)
if(!(condition)) {
std:ostringstream s;
s
<< msg;
SCPP_AssertErrorHandler(__FILE__,__LINE__,s.str().c_str());
}

#scpp_assert.cpp
void SCPP_AssertErrorHandler(const char *file_name,
unsigned line_no,
const char *msg){
//此处适合插入断点,合适情况下还可向一个日志文件写入相同的信息
#ifdef SCPP_THROW_EXCEPTION_ON_BUG
throw scpp::ScppAssertFailedException(file_name,
line_no,msg);
#else
cerr
<< msg << "in file "<<file_name <<
" #" <<line_no <<endl<<flush;
exit(
1);
#endif
}

#scpp.h
#ifdef SCPP_THROW_EXCEPTION_ON_BUG
#include


namespace scpp {
class ScppAssertFailedException :public std::exception {
private:
std::
string what_;
public:
ScppAssertFailedException(
const char *file_name, unsigned line_no,
const char *msg);
virtual void const char* getwhat() const throw() { return what_.c_str();}
virtual ~ScppAssertFailedException() throw() {}
}

}

#scpp_assert.cpp
#ifdef SCPP_THROW_EXCEPTION_ON_BUG
namespace scpp {
ScppAssertFailedException::ScppAssertFailedException(
const char *file_name,
unsigned line_no,
const char *msg) {
ostringstream s;
s
<< "SCPP Assertion failed with message " << msg <<" in file " <<file_name << " # " << line_no;
what_
=s.str();
}
}
#endif

我们可以看到宏接受了一个条件和一条错误消息。 如果条件为真,则什么也不做,如果为假,将向 ostringstream 输出一条错误消息,并调用错误处理函数。 这里有两个问题:

注意:要养成这样的习惯,单元测试也是类似的思路:边写代码边写安全检查和测试。 一种更具体的方法是在我们开始编写特定代码之前为其所有输入编写安全检查

c++判断字符串是否为空_c++ 判断是否为指数_c 判断对象是否为null

使用类似下面的代码进行测试:

#include 
#include
"scpp_assert.h"

using namespace std;
int main(int argc,char *argv[]) {
cout
<< "Hello,SCPP_ASSERT" << endl;

try {
double price=100.0 ; //合理价格
SCPP_ASSERT(0< price && price <=1e6,"Stock price " <<price <<" is out of range "); //条件成立时不执行

price
=-1;
SCPP_ASSERT(
0< price && price <=1e6,"Stock price " <<price <<" is out of range "); //条件不成立时执行并捕获异常
} catch (const exception& ex) {
cerr
<< "Exception caught in " << _FILE_ << " # "<< _LINE_ << ". "<< endl;
cerr
<< ex.what() << endl;
}
return 0;
}

//在SCPP_ASSERT宏中也可使用任何类的对象,只要它定义了<< 操作符,设计和测试如下:
/*
Test :
*MyClass obj(inputs);
*SCPP_ASSERT(obj.IsValid(),"Object "<< obj <<" is invalid.");
*/
class MyClass {
public:
bool IsValid() const ; //对象状态有效即返回true
//Implement constructors 、destructors
private:
int data;
friend std::ostream
operator << (std::ostream& os ,const MyClass& obj);
}
inline std::ostream
operator << (std::ostream& os ,const MyClass& obj) {
//执行一些任务,按被人理解的格式显示对象
os << obj.data;
return os;
}
/*
* Output :
* Hello,SCPP_ASSERT
* Exception caught in xxx.cpp #13 .
* SCPP assertion failed with message 'Stock price -1 is out of range ' in file xxx.cpp #13
*/

问:什么时候用

A:我们意识到代码中可能有大量的安全检查,有些是永久性的,有些是临时性的。 为了保持C++代码执行的效率和效果,在不同的执行阶段实施不同的策略:

代码实现如下:

#scpp_assert.h
#ifdef _DEBUG
#define SCPP_TEST_ASSERT_ON
#endif

#ifdef SCPP_TEST_ASSERT_ON
#define SCPP_TEST_ASSERT(condition,msg) SCPP_ASSERT(condition,msg)
#else
#define SCPP_TEST_ASSERT(condition,msg)

可以看到SCPP_ASSERT是永久性的安全检查,SCPP_TEST_ASSERT可以在编译时开启。

过去精选

免责声明:发表此文是为了传递更多的知识,以供交流学习。 如有来源标注错误或侵犯您的合法权益,请联系我们并提供权属证明,我们将及时更正和删除c++判断字符串是否为空,谢谢。