【质量】防御性编程
什么是防御性编程
防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,然后创建一个环境来测试错误,当预见的问题出现的时候通知你,并执行一个你指定的损害控制动作,如断言、停止程序执行,将用户重指向到一个备份的服务器,或者开启一个你可以用来诊断问题的调试信息。
防御性编程技巧
1> 使用好的编码风格和合理的设计
2> 不要仓促地编写代码
3> 不要相信任何人
4> 编码的目标是清晰,而不是简洁
5> 不要让任何人做他们不该做的修补工作
6> 编译时打开所有警告开关
7> 使用静态分析工具
8> 使用安全的数据结构
9> 检查所有的返回值
10>审慎地处理内存(和其他宝贵的资源)
11>在声明位置初始化所有变量
12>尽可能推迟一些声明变量
13>使用标准语言工具
14>使用好的诊断信息日志工具
15>审慎地进行强制转换
16>其他
a> 审慎地进行强制转换
b> 提供默认的行为
c> 检查数值的上下限
详细:https://www.cnblogs.com/zlhff/p/5455421.html
……对于面向用户的应用来说,我们确实需要在前端及后端同时进行数据流校验,且校验规则是相同的。如果我们的校验规则仅仅去写一次,就能够同时作用于前端及后端,那就很方便去维护了。我们需要一个中间件去配置这些校验的规则。简单举个xml的配置方式,如——
<!-- 这里的form的id对应表单的name属性值 -->
<form id="loginForm">
<!-- 这里的name对应页面表单的name属性值,required="true"表示该数据项是必填项,msg代表提示文本 -->
<input name="username" required="true" length="1,30" msg="30个字符以内"></input>
<input name="password" required="true" length="6,16" msg="6到16个字符以内"></input>
</form>
这样,不管是前端还是后端,我们可以通过解析这个xml文件来确保两者的校验规则是相同的。
……
https://www.cnblogs.com/jinguangguo/p/3304941.html
C/C++踩坑记录
坑 1
立即数左移越出范围。先看一段代码:
assert((1 << 3) == pow(2, 3));
assert((1 << 30) == pow(2, 30));
assert((1 << 62) == pow(2, 62));
先不运行,猜测一下问题在哪一行?
运行结果如下:
Assertion failed: (1 << 62) == pow(2, 62)
在 C 语言中,左移结果最大是 32 位。
坑 2
sprintf() 越界问题。
char buf[10]; float x = 1/3.0f; sprintf(buf, "cols = %f", x); printf(buf);
运行后,buf 会越界,出现地址异常!正确的做法是给 buf 一个更大的地址。 但是这类栈溢出在大型的工程中,防不胜防。其实可以在 C++ 中,考虑用更一种更安全的方式。
float f = 1 / 3.0f;
ostringstream ss;
ss << "num is " << f << endl;
cout << ss.str();
坑 3
坑 4
sizeof()
用于一个结构体时其值不是绝对的。与平台相关,也与编译指令相关。看例子:
#pragma pack(1)
struct pack_struct
{
char t;
uint32_t x;
};
#pragma pack()
输出是什么?
有很多同学一看,说这个容易, char
占用一个字节, uint32_t
占用 4 个字节,所以共占用 5 个字节。还有同学想到了 4 字节对齐,说应该是 4 的倍数,所以应该是 8。那正确的结果又是什么呢?
其实答案是:两者都有可能。与编译控制有关。要想结果得到 “5”, 用下面的:
#pragma pack(1)
struct pack_struct
{
char t;
uint32_t x;
};
#pragma pack()
要想得到 “8”, 把pack(1)
改成pack(4)
。
一般默认的编译参数,不同的平台是不一样的。所以要求我们不能写硬编码的代码。例如下面的代码将得不到我们想要的结果:
pack_struct arr_pack[8];
char *p_tmp = (char*)arr_pack;
pack_struct *pack_idx_1 = (pack_struct*) (p_tmp + 5);
示例有点儿绕,我们 j 是想要通过指针的方式得到数组 arr_pack 的第一个指针。这里用了硬编码,写成了 5。
这里硬编码的问题可能是,在其它的平台上不一定能正确工作!
为了方便移植,应该改写成:
pack_struct arr_pack[8];
char *p_tmp = (char*)arr_pack;
pack_struct *pack_idx_1 = (pack_struct*) (p_tmp + sizeof(pack_struct));
坑 5
浮点数的比较。某君写了如下代码:
float x = 1.333 - 1;
cout << x << endl;
if (x == 0.333)
{
cout << "x = " << 0.333 << endl;
}
else
{
cout << "x != " << 0.333 << endl;
}
请问一下输出是什么?
不用想,笔者在这里把它们写出来,肯定是有坑的,所以输出结果也是 “惊世骇俗” 的,输出为:
0.333
x != 0.333
看起来不可思议!!!问题来了,那怎样才能正确地作浮点数相等判断呢?也很简单,一般用偏离一个中心的距离小于某个精度来判断相等:
if (fabs(f1 - f2) < 预先指定的精度)
{
//...
}
实际一般推荐用 1e-5 作为精度(看各项目的精度要求啰)。
坑 7:栈被意外修改
看下面这段代码。你觉得Line 28
会异常吗? 实际结果是” 会 “!
这个就是我在工作中遇到的一个实际问题,当时一直监控一个变量,这个变量总是莫名其妙被更改了, 最后挖出来罪魁祸首就是一个 读 把栈破坏了。结果我的 “N” 成了一个无意义的超大数。
所以一定要严格控制好指针的越界行为,编译器无法知道你的意图,这个只有靠自己来把控,但也有一些方法来排查和防卫这种问题的发生。这是接下来要讲的内容。
内存泄露问题排查
通过一些工具去协助监控内存的分配和释放, 如:coverity (力荐,因为是我的前东家新思科技产的,当然也是最好用的,Valgrind (开源免费)等。
https://blog.csdn.net/GitChat/article/details/78737116