一道C语言安全编码题目

1、前言

最近在网上看到一道C语言题目,用C语言实现一个函数,给定一个int类型的整数,函数输出逆序的整数,例如输入123,则输出字符串”321″,,输入-123,则输出字符串”-321″。题目要求,不使用标准库,不得分配内存。当时觉得蛮简单的,这不就是类似字符串逆转嘛,自己尝试做了一下,测试发现,还是有很多地方考虑不周全。今天在此整理一下基础知识,作为一名安全开发人员,时刻需要注意代码的安全,防止有任何漏洞。题目给出的函数如下:

#include

const char * parseInt(int data)
{
    return "321";
}

int main()
{
    printf("%s\n", parseInt(123));
    return 0;
}

2、思考过程

写代码最怕的就是没有想好,一上来就写,在写的过程中不断的测试修改,这样很浪费时间。因此需要先好好想一下,这个题目到底考些什么呢?

(1)int类型的整数分为正数、0、负数,如何处理这些边界值

(2)整数与字符串之间的转换,如何将一个整数转换为一个字符

(3)如何返回一个const char * 类型的字符串

(4)当输入的整数超过int的范围如何处理

3、编码过程

开始写代码的思路如下:定义一个char类型的数组,用于保存结果。使用对10取余和除法操作依次获取每一位的数字,然后根据ASSIC码转换为字符。将字符拼接起来,返回字符串数组结果。编码实现如下:

const char * parseInt(int data)
{
    char str[16] = {0};
    int i = 0;
    if (data < 0) {
        data = -data;
        str[i++] = '-';
    }
    int tmp = data;
    while (tmp / 10) {
        char ch = tmp % 10 + 48;
        tmp = tmp / 10;
        str[i++] = ch;
    }
    str[i++] = tmp % 10 + 48;return str;
}

当初没有考虑那么多,编译发现出现如下错误:

一道C语言安全编码题目

一看编译错误,才意识到自己掉入坑中。题目要求返回一个字符串,而且不用分配内存。当时就想直接定义一个字符数组进行返回,而定义的str属于函数局部变量。

一个函数的局部变量都是存在stack中的,当这个函数调用过程结束时,这个局部变量都是要释放掉的,所以就会产生这样的warning,这个是和变量的life time相关的,所以解决方法有:

1.将char result[16]改为static型

2.使用malloc向heap申请,这些是需要caller用free去释放的

于是使用static 类型字符串,代码改进如下:

1 const char * parseInt(int data)
 2 {
 3     static char str[16] = {0};
 4     int i = 0;
 5
 6     if (data < 0) {
 7         data = -data;
 8         str[i++] = '-';
 9     }
10
11     int tmp = data;
12     while (tmp / 10 != 0) {
13         char ch = tmp % 10 + 48;
14         tmp = tmp / 10;
15         str[i++] = ch;
16     }
18     str[i++] = tmp % 10 + 48;
20     return str;
21 }

int main()
{
printf(“%s\n”, parseInt(123));
printf(“%s\n”, parseInt(12345678));
printf(“%s\n”, parseInt(-89790));
return 0;
}

测试结果如下:

一道C语言安全编码题目

改为static之后,编译成功,看输出的结果上看,前面两个输出是正确的,而第三个输出的结果是错误的。尼玛,再次掉入坑中,对static变量的应用不精通啊。为什么每次到看到结果后才想起来?

虽然在函数中定义了static局部变量,使得变量的变为静态stack存储区域,生命周期从函数中变成了这个程序的范围。但是static局部变量在函数第一次调用的时候会初始化,后面调用就不会了,直接使用了。因此导致了刚才的结果输出不对,复用了上次遗留的结果。

static静态局部变量属于静态存储方式,它具有以下特点:
(1)静态局部变量在函数内定义 它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
(2)允许对构造类静态局部量赋初值 例如数组,若未赋以初值,则由系统自动赋以0值。
(3)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。 根据静态局部变量的特点, 可以 看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的 值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成 意外的副作用,因此仍以采用局部静态变量为宜。

第一次调用函数,static变量,初始化。
第二次,及以后,调用函数,static变量,不会初始化。

继续改进代码,在函数中将static变量每次使用for循环进行初始化,改进代码如下:

1 #include
 2
 3 const char * parseInt(int data)
 4 {
 5     static char str[16] = {0};
 6     int i = 0;
 7
 8     int t = 0;
 9     for (; t < 16; t++) {
10         str[t] = 0;
11     }
12
13     if (data < 0) {
14         data = -data;
15         str[i++] = '-';
16     }
17
18     int tmp = data;
19     while (tmp / 10 != 0) {
20         char ch = tmp % 10 + 48;
21         tmp = tmp / 10;
22         str[i++] = ch;
23     }
24
25     str[i++] = tmp % 10 + 48;
26
27     return str;
28 }
29
30 int main()
31 {
32     printf("%s\n", parseInt(123));
33     printf("%s\n", parseInt(12345678));
34     printf("%s\n", parseInt(-89790));
35     return 0;
36 }

这次输出结果如下:

一道C语言安全编码题目

终于得到了正确答案,看似很简单的题目,折腾的这么久,才搞出来。扩展一下,大家看看如下这个输出什么呢:

printf("%s, %s, %s\n", parseInt(98989),parseInt(-4567), parseInt(123456));

这个结果是什么呢?为什么会这样呢?

printf("%s\n", parseInt(0x8FFFFFFF)); printf("%s\n", parseInt(0xFFFFFFFF));

这个结果是什么呢?为什么会这样呢?

int 类型4个字节,32位组成。int的最高位作为符号位,需要特殊处理。

实际运行结果如下:

一道C语言安全编码题目

Original: https://www.cnblogs.com/Anker/p/7039849.html
Author: Rabbit_Dale
Title: 一道C语言安全编码题目

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/536557/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C语言常见字符串 — 好玩但没怎么用过

    C语言strspn()函数:计算字符串str中连续有几个字符都属于字符串accept C语言strcspn()函数:计算字符串str中连续有几个字符都不属于字符串accept C语…

    C语言 2023年5月29日
    068
  • 【专栏】重学Go语言文章导航

    代码改变世界 Cnblogs Dashboard Login 2017-11-17 09:42 李永京 阅读(2113 ) 评论() 编辑 大纲 专栏 开篇:学习基础技能树意义 0…

    C语言 2023年5月29日
    074
  • Kruskal算法(一)之 C语言详解

    本章介绍克鲁斯卡尔算法。和以往一样,本文会先对克鲁斯卡尔算法的理论论知识进行介绍,然后给出C语言的实现。后续再分别给出C++和Java版本的实现。目录1. 最小生成树2. 克鲁斯卡…

    C语言 2023年5月29日
    074
  • C语言-转

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/JohnABC/p/8458883.htmlAuthor…

    C语言 2023年5月29日
    077
  • [C语言]支持命名参数的函数调用

    对于参数较多的函数,如UI库函数,你很难去记忆每个位置的参数类型和意义,尤其在你的IDE比较简陋的开发环境下,尤为痛苦,可能你需要频繁的查询文档。 像Python这样语言,原生支持…

    C语言 2023年5月29日
    068
  • JavaSE 可变参数的方法重载

    1 /** 2 * 可变参数的方法重载 3 */ 4 class A { 5 public void test(int a, int b) { 6 System.out.print…

    C语言 2023年5月29日
    088
  • C语言结构联合位字段知识体系总结大学霸IT达人

    C语言结构联合位字段知识体系总结大学霸IT达人 C语言的基础类型中只能去定义单一类型的变量用于指代数据,但在现实生活中我们常常要处理的数据却会包含多种类型的数据。 例如,公司员工的…

    C语言 2023年5月29日
    062
  • c语言文件处理 (上课用)

    源文件: include //写函数void writetext(FILE *fw){char str[80];gets(str);while(strcmp(str,”…

    C语言 2023年5月29日
    081
  • C语言字符串操作总结大全

    转:https://www.cnblogs.com/lidabo/p/5225868.html 1)字符串操作strcpy(p, p1) 复制字符串strncpy(p, p1, n…

    C语言 2023年5月29日
    052
  • C语言:结构体和共用体

    这是很基础的教程,我只是写给自己看,作为一个学习笔记记录一下,如果正在阅读的你觉得简单,请不要批评,可以关掉选择离开 如何学好一门编程语言 掌握基础知识,为将来进一步学习打下良好的…

    C语言 2023年5月29日
    0127
  • C语言初学者代码中的常见错误与瑕疵(22)

    posted @2015-02-06 22:17 garbageMan 阅读(666 ) 评论() 编辑 Original: https://www.cnblogs.com/pme…

    C语言 2023年5月29日
    090
  • 【揭秘】C语言类型转换时发生了什么?

    ID:技术让梦想更伟大作者:李肖遥链接:https://mp.weixin.qq.com/s/ZFf3imVaJgeesuhl1Kn9sQ 在C语言中,数据类型指的是用于声明不同类…

    C语言 2023年5月29日
    069
  • 邻接表有向图(一)之 C语言详解

    本章介绍邻接表有向图。在”图的理论基础“中已经对图进行了理论介绍,这里就不再对图的概念进行重复说明了。和以往一样,本文会先给出C语言的实现;后续再分别给出C…

    C语言 2023年5月29日
    073
  • C语言字符串处理函数

    ssprintf和sscanf函数的使用总结: 1、前言 我们经常涉及到数字与字符串之间的转换,例如将32位无符号整数的ip地址转换为点分十进制的ip地址字符串,或者反过来。从给定…

    C语言 2023年5月29日
    098
  • 拓扑排序(一)之 C语言详解

    拓扑排序(Topological Order)是指,将一个有向无环图(Directed Acyclic Graph简称DAG)进行排序进而得到一个有序的线性序列。 这样说,可能理解…

    C语言 2023年5月29日
    071
  • Prim算法(一)之 C语言详解

    普里姆(Prim)算法,和克鲁斯卡尔算法一样,是用来求加权连通图的最小生成树的算法。 基本思想对于图G而言,V是所有顶点的集合;现在,设置两个新的集合U和T,其中U用于存放G的最小…

    C语言 2023年5月29日
    078
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球