嵌入式软件十大编程低级错误C语言-上篇

1. 概述


本文讨论的十大编程低级错误包含:
1)    空指针错误;
2)    数据未校验的错误;
3)    数组越界访问错误;
4)    内存泄漏;
5)    资源泄漏;
6)    使用未初始化的变量问题;
7)    忘记检查返回值的问题;
8)    编码安全性及不恰当的错误处理;
9)    互斥锁使用不当造成的异常
10)    编码逻辑错误;

 
2.    空指针错误


2.1.    定义(说明)


空指针是一个指向内存中地址为NULL的指针,也称为未初始化指针。在程序中,如果一个指针没有被明确地赋予一个有效的内存地址,它就会被视为空指针。当试图使用空指针来访问或操作内存中的数据时,通常会导致程序崩溃或出现未定义行为。


2.2.    产生该问题的原因


空指针通常被应用在如下场景:
1)    当初始化指针变量时,该指针变量不指向有效的内存地址,则将其赋值为空指针;
2)    在对指针进行引用之前,用于判断指针是否有被正确赋值,即指针的有效性判断;
3)    作为函数参数传递以及在不想传递实际内存地址时、从函数返回时使用;
4)    作为一个空间的尾部,某个数据结构的结束标志。

一个指针,只有在真正指向了一块有意义的内存后,我们才能对它取内容。在空指针使用不当的情况下,通常会引起空指针错误,其通常是有以下几种情况:


1)    指针没有被初始化;或者指针变量不指向有效的内存地址时,没有被赋值为NULL;
2)    指针指向的内存区域被释放或销毁,但指针本身没有被置为NULL;
3)    指针超出了其作用域,导致指针变为野指针(指向一个错误位置的指针);
4)    所调用的函数返回为空指针,但程序未对其进行处理。


2.3.    典型案例分析


【问题描述】【接口测试】命令字dmgr.writePIIDS,下发params为空的数据,好帮手死机
【问题定位】未对指针判空,导致程序重启
 
【纠正措施】指针增加判空处理
【举一反三】使用接口获取指针类型的数据,应该都加上判空的逻辑


2.4.    应遵循的原则


空指针通常会导致灾难性的问题。避免空指针错误的原则是在使用指针之前确保指针已经被正确初始化,并且在释放指针所指向的内存后将指针置为NULL。这样可以有效地减少空指针错误的发生。以下是一些遵循的原则:
1)    在声明指针时初始化为NUL:在声明指针变量时,应该立即将其初始化为NULL,这样可以避免未初始化指针的问题。
2)    在动态分配内存后检查指针是否为NULL:在使用malloc()或calloc()等函数动态分配内存时,应该检查返回的指针是否为NULL,以确保内存分配成功。调用那些返回值为指针类型的函数时,同样要做好指针检查;
3)    在释放内存后将指针置为NULL:在使用free()函数释放动态分配的内存后,应该将指针置为NULL,以避免出现野指针的问题;
4)    避免悬空指针:在指针指向的内存被释放后,应该避免继续使用该指针,以避免出现悬空指针的问题。
遵循以上原则可以帮助减少空指针错误的发生,提高程序的稳定性和可靠性。


3.    数据未校验的错误


3.1.    定义(说明)


数据未校验或未判空处理的错误是指在代码中没有对传入的参数、从文件中读取的数据、返回的参数的数据进行有效性判定或判空处理。
在C语言中,数据未校验错误往往会导致如下后果:
1)    缓冲区溢出:当向一个固定大小的缓冲区写入超过其容量的数据时,可能会导致缓冲区溢出。这种情况可能会导致数据覆盖、程序崩溃或者被利用进行恶意攻击。
2)    数组越界访问:当访问数组时,如果访问超出数组边界的元素,可能会导致未定义行为或程序崩溃。
3)    空指针引用:当使用未初始化或者被释放的指针时,可能会导致空指针引用错误,导致程序崩溃或者未定义行为。
4)    未检查的输入:当程序接收外部输入数据时,如果没有进行足够的有效性检查,可能会导致恶意输入或者无效输入导致的错误。
5)    整数溢出:当进行整数运算时,如果结果超出了整数类型的表示范围,可能会导致整数溢出错误。
6)    资源泄漏:当程序动态分配内存或者打开文件等资源时,如果没有正确释放资源,可能会导致资源泄漏,最终导致系统资源耗尽。


3.2.    产生该问题的原因


数据未校验通常包含如下几种情况:
1)    没有对数据的边界值做判断;
2)    数据长度的校验,如:密码长度;
3)    申请内存时,没有对申请内存的大小做校验,如申请了超大的内存;
4)    空指针判断;
5)    数据字段为空时,没有做判断;
6)    查询数据库时,部分字段不存在,没有判空处理等。


3.3.    典型案例分析


【问题描述】无线智能终端使用一个月左右,出现TUTK对讲无声问题
【问题定位】在代码中,针对时间戳(ms)级别的数据,使用 int型作为变量的声明,int 型的取值范围为 -2147483648 到 2147483647 ; (2147483647 +1 )上溢出后变为 -2147483648,(-2147483648 -1)下溢出后变为 2147483647;2147483647 按毫秒计算大概位置25天左右(25*24*60*60*1000) 超过25天后 形成上溢,变为负数,最终导致缓存中针对时间的计算出现问题,导致声音无法送到AO。
 
【纠正措施】将int型修改为uint64_t 
【举一反三】针对时间戳的赋值,应当敏感,需要考虑其可能的边界,使用uint64_t 保证其无法溢出,或者溢出后的情况要纳入考虑范围


3.4.    应遵循的原则


为了避免数据未校验错误,程序员可以采取一些措施,如合理设计数据结构、进行输入验证、使用安全的库函数、避免硬编码数组大小等。同时,进行代码审查、测试和使用静态分析工具也可以帮助发现潜在的数据未校验错误。
数据校验可以有效地提高程序的安全性和稳定性。以下是一些数据校验应该遵循的原则:
1)    输入验证:对于用户输入的数据,应该进行有效性验证,确保数据符合预期的格式、范围和类型。避免接受不可信的输入数据,防止恶意输入或非法数据导致的安全问题。
2)    范围检查:对于数值型数据,应该进行范围检查,确保数据在合法的范围内。避免整数溢出、浮点数精度丢失等问题。
3)    长度检查:对于字符串型数据,应该进行长度检查,确保数据不会导致缓冲区溢出。避免字符串拷贝、连接等操作导致的缓冲区溢出问题。
4)    数据类型检查:确保数据的类型与预期一致,避免数据类型转换错误导致的问题。
5)    内存管理检查:对于动态分配的内存,应该确保正确分配和释放内存,避免内存泄漏和悬空指针问题。
6)    错误处理:在数据校验失败时,应该进行适当的错误处理,例如返回错误码、抛出异常等,避免程序继续执行导致更严重的问题。
7)    使用安全的库函数:避免使用不安全的库函数,如strcpy()、gets()等,应该使用更安全的替代函数,如strncpy()、fgets()等。
8)    防御性编程:采用防御性编程的思想,假设用户输入是不可信的,对输入数据进行严格的校验和过滤,确保程序的健壮性。
遵循以上原则可以帮助有效地进行数据校验,提高程序的安全性和可靠性,避免数据校验导致的错误和安全问题。


4.    数组越界访问错误


4.1.    定义(说明)


数据越界访问错误是指试图访问超出其合法范围的内存位置的情况。这种超出其边界范围导致未定义的行为,通常发生在以下情况下:
1)    数组越界访问:当程序尝试访问数组中超出其边界范围的元素时,就会发生数组越界访问错误。例如,访问数组的负索引或超过数组长度的索引位置。
2)    指针越界访问:当程序使用指针访问内存时,如果指针指向的内存位置超出了其分配的范围,就会发生指针越界访问错误。这可能会导致程序访问到未分配的内存区域或其他程序的内存空间,造成程序崩溃或安全问题。

数据越界访问错误可能导致以下问题:
1)    程序崩溃:访问未分配的内存区域可能导致程序崩溃,因为操作系统会检测到非法访问而终止程序的执行。
2)    内存损坏:越界访问可能会导致内存损坏,使得程序的其他部分数据被破坏,从而影响程序的正确执行。
3)    安全漏洞:恶意用户可以利用数据越界访问错误来修改程序的控制流,执行恶意代码或获取敏感信息,造成安全漏洞。


4.2.    产生该问题的原因


产生数据越界访问错误的常见原因包括但不限于以下几点:
1)    未正确计算数组或指针的索引:程序员在访问数组元素或指针指向的内存时,未正确计算索引值,导致越界访问。
2)    内存分配不足或释放后继续访问:当程序未正确分配足够的内存空间给数组或指针,或者在释放内存后继续访问已释放的内存区域,都可能导致越界访问错误。
3)    循环中的索引错误:在循环中,索引计算错误可能导致数组越界访问。例如,循环的终止条件错误导致循环超出数组范围。
4)    复杂数据结构中的访问错误:在使用复杂数据结构(如多维数组、结构体、链表)时,未正确计算访问的位置或指针可能导致越界访问错误。
5)    字符串操作错误:在对字符串进行操作时,未正确处理字符串的长度和结尾符可能导致越界访问错误。
6)    多线程并发访问:在多线程环境下,未正确同步访问共享数据结构可能导致越界访问错误。
7)    外部输入验证不足:未对外部输入进行充分验证和边界检查,导致恶意输入或异常情况触发数据越界访问错误。


4.3.    典型案例分析


【问题描述】【UI挂机】app修改房间名字,终端进入全屋界面查看,概率出现挂机
【问题定位】房间名过长导致数组越界,房间名称最长10个字,故占用30个字节,本身32应该是够的,但是实际上还要算上“#3AE1F9 %s#”(颜色定义)的长度,故tmp一定会超出    
 
【纠正措施】
当前修改是将32修改为64,实际上是不合理的,只是因为最长就三十个字符,故也能接受,实际上应该使用如图的修改。
【举一反三】 
开发接口的时候应当采用类似单元测试一样,测试其边界条件


4.4.    应遵循的原则


为了有效防止数据越界访问错误,程序员应该遵循以下原则:
1)    边界检查:在访问数组元素或指针指向的内存时,始终进行边界检查,确保访问的索引值在合法范围内。
2)    索引计算:确保正确计算数组或指针的索引值,避免出现偏移量错误导致的越界访问。
3)    内存分配和释放:正确管理内存的分配和释放,确保分配足够的内存空间给数组或指针,并在释放后避免继续访问已释放的内存区域。
4)    字符串处理:对字符串操作时,保证正确处理字符串的长度和结尾符,避免越界访问错误。
5)    循环控制:在循环中确保正确控制索引值,避免循环超出数组范围。
6)    复杂数据结构:在使用复杂数据结构时,确保正确计算访问的位置或指针,避免越界访问错误。
7)    多线程同步:在多线程环境下,正确同步访问共享数据结构,避免并发访问导致的越界访问错误。
8)    输入验证:对外部输入进行充分验证和边界检查,避免恶意输入或异常情况触发数据越界访问错误。
9)    使用安全函数:使用安全函数或库来处理数组和字符串操作,如使用memcpy而不是strcpy,以减少越界访问的风险。
为了避免数据越界访问错误,程序员应该谨慎编写代码,进行充分的边界检查和索引计算,确保数组和指针访问的范围在合法范围内,避免未分配内存访问、循环错误、字符串处理错误等问题,以确保程序的正确性和安全性。同时,做好代码审查、使用工具和技术如静态代码分析、内存检测工具、单元测试等可以帮助发现和纠正潜在的数据越界访问错误。


5.    内存泄漏的问题


5.1.    定义(说明)


内存泄漏是指在程序运行过程中,由于程序未正确释放动态分配的内存空间,导致这部分内存无法被再次使用,从而造成系统内存资源的浪费和耗尽。
5.2.    产生该问题的原因
内存泄漏是由于程序中未正确管理动态分配的内存空间而导致的问题。以下是一些常见的导致内存泄漏的原因:
1)    未释放动态分配的内存:程序使用malloc、new等函数动态分配内存空间,但在使用完毕后未通过free、delete等函数释放内存。
2)    指针引用丢失:在程序中,指针引用的对象被释放或指针被重新赋值,导致原本指向的内存空间无法再被释放。
3)    循环引用:当两个或多个对象之间存在循环引用关系时,如果采用引用计数等内存管理方式,可能导致内存泄漏。
4)    覆盖指针:在程序中,将一个指针指向一个新的内存地址时,原来指向的内存地址未释放,导致内存泄漏。
5)    遗忘释放资源:程序中可能会使用文件、网络连接、数据库连接等资源,如果在使用完毕后未正确释放这些资源,也会导致内存泄漏。
6)    异常情况处理不当:在程序中出现异常情况,如异常退出、崩溃等,可能导致未能正确释放内存空间,从而发生内存泄漏。
7)    内存分配和释放不匹配:程序中存在内存分配和释放不匹配的情况,例如使用malloc分配内存后却使用delete释放,或者使用new[]分配内存后却使用delete释放,都会导致内存泄漏。
为了避免内存泄漏,程序员应该注意正确管理动态分配的内存空间,及时释放不再使用的内存,避免指针引用丢失和循环引用等问题。同时,使用内存检测工具、静态代码分析工具等可以帮助发现和解决潜在的内存泄漏问题。
5.3.    典型案例分析
【问题单号】http://172.30.5.51/bug-system/bugTable?id=14019
【问题描述】【大系统】【内存】同时控制100个设备(普通灯+智能照明),出现内存泄漏现象
【问题定位】申请内存后没有释放相关内存导致
 
【纠正措施】 针对有开辟内存的场景,调用free接口释放
【举一反三】 内存的开辟和释放必须是成对出现的,遵循谁申请谁释放的原则 
5.4.    应遵循的原则
为了避免内存泄漏,程序员应该遵循以下原则:
1)    分配内存就要释放内存:使用`malloc`、`new`等函数动态分配内存空间后,一定要记得在合适的时机通过`free`、`delete`等函数释放内存,确保内存资源得到正确释放。
2)    避免指针引用丢失:在程序中使用指针时,要确保指针引用的对象在合适的时机被释放,避免指针引用丢失导致内存泄漏。
3)    避免循环引用:在设计程序时,要注意避免对象之间存在循环引用关系,特别是在使用引用计数等内存管理方式时,避免出现循环引用导致的内存泄漏。
4)    良好的异常处理:在程序中要处理异常情况,确保在异常退出或崩溃时能够正确释放内存资源,避免发生内存泄漏。
5)    使用内存检测工具:在开发过程中,可以使用内存检测工具(如Valgrind、Dr.Memory等)来检测程序中的内存泄漏问题,及时发现并解决潜在的内存泄漏。
遵循以上原则可以帮助程序员有效地避免内存泄漏问题,提高程序的稳定性和性能。为了避免内存泄漏,程序员应该注意及时释放动态分配的内存空间,避免指针引用丢失,正确处理循环引用等情况。同时,使用内存检测工具、静态代码分析工具等可以帮助发现和解决潜在的内存泄漏问题。

 

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值