关于枚举常量手误带来的错误

简介: 关于枚举常量手误带来的错误

前言

记录2020年5月30日,肯哥在群里面分享的一个因为手误带来的bug。


问题描述

肯哥原话:

又到了每天的open话题讨论时刻,一起在摸鱼中学点东西,今天我们来聊一个话题:一不小心的手误,代码有时能跑,有时又会出问题,到时是怎么回事?你是否也遇到过类似的奇葩问题?欢迎大家来聊聊你在实际的学习和工作中遇到的类似问题场景。

又到了每天的open话题讨论时刻,一起在摸鱼中学点东西,今天我们来聊一个话题:一不小心的手误,代码有时能跑,有时又会出问题,到时是怎么回事?你是否也遇到过类似的奇葩问题?欢迎大家来聊聊你在实际的学习和工作中遇到的类似问题场景。


/* 一个信息的记录表 */
static const char *g_msg_tab[] = 
{
  "SUCESS",
  "NO SUCH FILE",
  "IO ERROR",
  "BAD FILE NUM",
  "OUT OF MEM"
  "FILE EXIST",
  "NO SUCH DEVICE",
  "UNKNOWN ERROR",
};
void send_err_msg_out(int err_index)
{
  char msg[16] = {0};
  snprintf(msg, sizeof(msg), "%s", g_msg_tab[err_index]);
  //send out ...
}

问题分析

(1)说实话,这个代码我看了很久没发现问题所在。后面是群里面大佬提醒少了一个逗号,才找到的。


(2)在枚举常量的定义中,少了一个’ ,’ 不应该会报错吗?这个是群里面大佬都在疑惑的一点,但是实操之后发现。居然是可以跑的。

因为肯哥的代码没有main函数,所以无法直接运行。因此我在这里增加了main函数。

这里需要注意一个问题,好像老版本的编译器编译snprintf函数会报错,我懒得处理了,就直接放在Linux中跑了。


#include <stdio.h>
/* 一个信息的记录表 */
static const char *g_msg_tab[] =
{
        "SUCESS",
        "NO SUCH FILE",
        "IO ERROR",
        "BAD FILE NUM",
        "OUT OF MEM"
        "FILE EXIST",
        "NO SUCH DEVICE",
        "UNKNOWN ERROR",
};
void send_err_msg_out(int err_index)
{
        char msg[16] = { 0 };
        snprintf(msg, sizeof(msg), "%s", g_msg_tab[err_index]);
        //send out ...
}
int main()
{
        printf("hello world\r\n");
        send_err_msg_out(0);
        return 0;
}


问题带来的结果

(1) 在枚举常量中少些了一个’,’ 我们发现并没有产生报错,所以我决定将枚举常量的第四个参数打印出来,看看是什么结果。

(2)后面发现,第四个字符串变成了两个字符串的叠加!

(3)肯哥后面揭晓答案的时候说,,编译器远比我们想象的更聪明,这里面的问题点是第5行字符串少了一个逗号,虽然编译没有问题,但是编译器会把第5行字符串和第6行字符串合并成一个字符串,存放在好g_msg_tab[4]的位置。

如何写能够揭露这个问题?

bug解决方案

(1) 肯哥后面说,因为这里定义字符串数组很巧妙,用了*xxx[ ]这种自适应元素个数的方法,即[ ]里面没有定个数,由编译器在编译的时候自行决定空间。

(2)而如果使用标准的二维数组来定义,可能这个错误问题早就暴露了,即形如 xxx[8][16]。

(3)可能这个时候有些人就有疑问了,为啥写成二维数组就能够暴露问题呢?

(4)首先,我们需要知道一个基本概念,C语言的字符串是什么?C语言的字符串本质上就是一个指针,这个指针指向一个.rodata段,.rodata段是一个特殊的段,用于存储只读的数据,如字符串常量和全局常量。

(5) g_msg_tab[]就是一个指针数组,用于存放这些字符串。而我们如果用二维数组来存放,二维数组的不也可以进似理解为一个指针数组吗?(注意,有区别!)不过这个指针指向的区域大小有规定,以xxx[8][16]为例,这个指针数组指向的区域大小最多16个字节。而 g_msg_tab[]中的指针指向的区域没有大小规定,只要别超出.rodata段存储大小即可。

(6)知道指针数组和二维数组的区别之后,我们应该不难得出结论。这个问题如何暴露呢?通过限制指针指向的区域大小。

这个方式真的有效吗

(1)可能已经有人想到了,这种方法真的能够杜绝这样的问题吗?既然二维数组能够规定存入位置的指定字符个数,那么我就让存放的数据恰好等于指定的字符个数不就行了吗?

(2)于是我做了如下更改,本来 "OUT OF MEM"和"FILE EXIST"合并之后的数据是大于16个字符的。我现在让他恰好等于16个字符试试。

(3)结果发现,编译通过了。但是却多打印了一行。这个又是什么问题呢?


(4)经过思考之后,我突然想到。我printf使用的是%s。而%s的工作原理是检测到“\0”才停止。我这里因为恰好为16个字符,所以“\0”被覆盖了,只有多的哪一行才有。因此这个二维数组实际上指向的区域只能存放15个字符。


总结

(1)其实这个就是一个C语言的小知识点,只不过很容易让人忽略。至于对于这个问题如何避免,我没有想到合适的方法,所以我只能建议各位一定要细心。

(2)这个bug恶心在于,编译器无法提示,所以如果出现莫名奇妙的问题。只能提醒各位检查检查枚举常量是否忘记写了一个’,'。

目录
相关文章
|
7月前
|
存储 开发者
如何确定常量的类型
在编程中,常量是程序中值不会改变的量。确定常量的类型是非常重要的,因为它直接影响了程序的效率和可读性。选择正确的常量类型可以确保程序占用更少的内存,运行更快,并且更容易理解和维护。
73 2
|
7月前
|
存储 编译器 Shell
【C++基础语法 枚举】解析 C/C++ 中枚举类型大小值
【C++基础语法 枚举】解析 C/C++ 中枚举类型大小值
87 0
|
存储
3.2.6 怎样确定常量的类型
3.2.6 怎样确定常量的类型
49 0
|
2月前
|
安全 Java
什么是枚举?
什么是枚举?
43 3
自定义类型枚举(上)
自定义类型枚举
37 0
|
C语言 C++
自定义类型枚举(下)
自定义类型枚举
42 0
|
Java 编译器 C++
常量接口 vs 常量类 vs 枚举区别
把常量定义在接口里与类里都能通过编译,那2者到底有什么区别呢?
84 0
【C#视频】常量、枚举、结构体、数组
【C#视频】常量、枚举、结构体、数组
枚举常量及其应用
枚举常量及其应用
121 0