【质量】防御性编程

什么是防御性编程

防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,然后创建一个环境来测试错误,当预见的问题出现的时候通知你,并执行一个你指定的损害控制动作,如断言、停止程序执行,将用户重指向到一个备份的服务器,或者开启一个你可以用来诊断问题的调试信息。

 

防御性编程技巧

 

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

 

posted on 2022-10-04 01:25  bdy  阅读(144)  评论(0编辑  收藏  举报

导航