内存越界是软件系统主要错误之一,其后果往往不可预料且非常严重。更麻烦的是,它出现的时机是随机的,表现出来的症状是随机的,而且造成的后果也是随机的,这会使程序员很难找出这些 Bug 的现象和本质之间的联系,从而给 Bug 的定位带来极大的困难。一般情况下,内存越界访问可分如下两种:

读越界,即读了不属于自己的数据。如果所读的内存地址是无效的,程序立刻崩溃;如果所读内存地址是有效的,在读的时候不会马上出现问题,但由于读到的数据是随机的,因此它会造成不可预料的后果。

写越界,又称为缓冲区溢出,所写入的数据对别的程序来说是随机的,它也会造成不可预料的后果。

避免数组越界

数组越界错误主要包括数组下标取值越界和指向数组的指针的指向范围越界。

数组下标取值越界主要是指访问数组时,下标的取值不在已定义好的数组的取值范围,而访问的是无法获取的内存地址。例如 int a[10],此数组 a 的下标取值范围是 [0,9]。若取值不在这个范围,就出现越界错误。

指向数组的指针的指向范围越界表示当定义的指针 p 若指向了数组的首地址时(即 p=a),若对其不断进行操作 p++,则最后会导致指针 p 指向大于该数组范围的上界,从而使程序访问了数组以外的存储单元,造成数组越界。

如下面的示例代码所示:

#define MAX_BUF_SIZE 10

int main(void)

{

int i = 0;

int a[MAX_BUF_SIZE] = { 0 };

for (i = 0; i <= MAX_BUF_SIZE; i++)

{

a[i] = i;

printf("%d", a[i]);

}

return 0;

}

上面的示例代码就是一个典型的数组下标取值越界。其中,因为 MAX_BUF_SIZE 被定义为 10,所以 int a[MAX_BUF_SIZE] 定义了 10 个元素大小的数组。由于 C 语言中数组的索引是从 0 开始的,所以只能访问 a[0] 到 a[9]。当“i=10”时,访问 a[10] 就造成越界错误。因此,应该修改成如下形式:

#define MAX_BUF_SIZE 10

int main(void)

{

int i = 0;

int a[MAX_BUF_SIZE] = { 0 };

for (i = 0; i < MAX_BUF_SIZE; i++)

{

a[i] = i;

printf("%d", a[i]);

}

return 0;

}

除此之外,在设置缓冲区大小时,要考虑各种应用场合,特别是考虑到函数参数的边界条件,按最大的可能分配空间,能够利用程序计算的,尽量自动计算。

避免 sprintf、vsprintf、strcpy、strcat 与 gets 越界

前面已经阐述过,C 语言提供的字符串库函数 sprintf、vsprintf、strcpy、strcat 与 gets 等非常危险,很容易导致内存越界,应该尽量使用安全的字符串库函数 snprintf、strncpy、strncat 与 fgets 来替换它们。

如下面的示例代码所示:

char buf[250];

sprintf(buf, "*** File:%s Line : %d ****", __FILE__, __LINE__);

其中,“__FILE__”在预编译时,被编译时的目录名和源文件名代替,但目录和文件名的长度可变,很可能超出 250 字节,从而导致内存越界。因此,应该使用 snprintf 来替换 sprintf 函数,指定缓冲区的大小,确保内存不会越界。如下面的示例代码所示:

snprintf(buf, MAX_BUF_SIZE - 1, "*** File:%s Line : %d ****", __FILE__, __LINE__);

buf[MAX_BUF_SIZE - 1] = '\0';

避免memcpy与memset函数长度越界

对于 memcpy 与 memset 函数,在使用的时候一定要确保长度不要越界。如下面的示例代码所示:

char a[80];

char b[100];

/*a的长度小于b的长度,发生越界*/

memcpy(a,b,sizeof(b));

很明显,b 的长度是 100,而 a 长度是 80,执行语句“memcpy(a,b,sizeof(b))”时,由于 a 的长度小于 b 的长度,所以将导致程序内存越界。因此,必须确保 a 的长度大于 b 的长度,又或者是 a 和 b 的长度保持一致。由于是字符串拷贝,因此还可以改用 strncpy 函数。如下面的示例代码所示:

char a[MAX_BUF_SIZE];

char b[MAX_BUF_SIZE];

strncpy(a, b, MAX_BUF_SIZE);

a[MAX_BUF_SIZE - 1] = '\0';

避免忽略字符串最后的'\0'字符而导致的越界

在 C 语言中,字符串是一个以'\0'字符结尾的字符数组。但是,当使用 strlen 库函数来获取字符串的长度时,其长度值并不包含'\0'字符。这就导致我们经常因为不小心而忽略了字符串最后的'\0'字符。

如下面的示例代码所示:

int main(void)

{

char * str = "abcdefghijk";

char c[20];

memcpy(c, str, strlen(str));

printf("%s:%d\n", c, strlen(c));

return 0;

}

在上面的代码中,“strlen(str)”获取的是 str 真实的字符串长度,不包括最后的'\0'字符。因此,当执行“memcpy(c,str,strlen(str))”语句时,并没有将 str 整个字符串复制到 c 中。再加上这里的字符数组 c 并没有进行初始化,所以最后执行“printf("%s:%d\n",c,strlen(c))”语句时,其运行结果将出乎意料,如图 1 所示。

图 1

但如果在“strlen(str)”获取的str真实的字符串长度后再加上 1(即包括最后的 '\0' 字符),情况就不一样了,如下面的示例代码所示:

int main(void)

{

char * str = "abcdefghijk";

char c[20];

memcpy(c, str, strlen(str)+1);

printf("%s:%d\n", c, strlen(c));

return 0;

}

运行结果为:

abcdefghijk:11

除此之外,在使用 strncpy 等安全函数时,当复制字符串到达指定的长度时,不会在目标字符串结尾添加 '\0' 字符,必须手工进行添加 '\0' 字符。当然,可以在申请内存时,将最后一个字节置为 '\0' 字符;也可以在调用 strncpy 函数后,紧接着赋 '\0' 字符。

c语言内存越界例子,内存越界的可能情况分析,C语言内存越界详解相关推荐

  1. 内存分配策略(一):JVM栈桢及方法调用详解

    JVM 线程堆栈分析过程详解 在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因.在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术.在线程堆栈中 ...

  2. hashmap remove 没释放内存_java从零开始手写 redis(13)HashMap 源码原理详解

    为什么学习 HashMap 源码? 作为一名 java 开发,基本上最常用的数据结构就是 HashMap 和 List,jdk 的 HashMap 设计还是非常值得深入学习的. 无论是在面试还是工作中 ...

  3. C/C++程序内存布局(data段,bss段,text段)以及static关键字详解

    目录 1.内存布局 1.1 data段(可读可写) 1.2 text段(只读) 1.3 bss段(可读可写) 1.4 堆区 1.5 栈区 1.6全局区/静态区 1.7 字符串常量区 1.8 代码区 1 ...

  4. 一键获取linux内存、cpu、磁盘IO等信息脚本编写,及其原理详解

    一.脚本 今天主要分享一个shell脚本,用来获取linux系统CPU.内存.磁盘IO等信息. #!/bin/bash # 获取要监控的本地服务器IP地址 IP=`ifconfig | grep in ...

  5. C语言指针面试题解析(万字超多题,每题都有详解)

    目录 零.前言 1.整型数组 2.字符数组 1.strlen函数 2.arr[]={'a','b','c'....}型 1.sizeof()计算 2.strlen()计算 3.char arr[]=& ...

  6. c语言常量的正确表示const,C语言中的const和free用法详解

    注意:C语言中的const和C++中的const是有区别的,而且在使用VS编译测试的时候.如果是C的话,请一定要建立一个后缀为C的文件,不要是CPP的文件.因为,两个编译器会有差别的. 一.C语言中的 ...

  7. 性能分析工作strace命令用法详解及使用例子

    1 功能说明 strace 命令是一种强大的工具, 能够显示任何由用户空间程式发出的系统调用. strace 显示这些调用的参数并返回符号形式的值. strace 从内核接收信息, 而且无需以任何特别 ...

  8. C语言的变量类型(int、short、char、float...)及变量类型转换详解

    前言 单片机的基本功能是进行数据处理,而数据在进行处理时需要先存放到单片机的存储器中.所以在编写程序时对变量与常量都要先声明数据类型,以便把不同的数据类型定位到嵌入式处理器的不同存储区中. 具有一定格 ...

  9. c语言stl模板,c/c++开发分享C++ 标准模板库 STL 顺序容器详解

    c++ 标准模板库 stl 顺序容器 容器 顺序性 重复性 支持迭代器 vector 动态数组 无序 可重复 随机访问迭代器 deque 双向队列 无序 可重复 随机访问迭代器 list 双向链表 无 ...

  10. go语言导入git包_使用go module导入本地包的方法教程详解

    go module 是Go1.11版本之后官方推出的版本管理工具,并且从 Go1.13 版本开始, go module 将是Go语言默认的依赖管理工具.到今天 Go1.14 版本推出之后 Go mod ...

最新文章

  1. 最全面的缓存架构设计
  2. 报名 | 挑战极限,参加2天清华数据Hackathon,赢得4万元奖金
  3. ggplot01:R语言坐标轴离散、连续与图例离散连续的区分
  4. Get busy living or get busy dying
  5. U_boot 的 bootcmd 和bootargs参数详解
  6. Catalan数(卡特兰数)
  7. 第一百二十一期:当新闻报道用上AR 技术,能为读者带来什么?
  8. 3ds max制作宋惠乔的教程----作者: 火星时代 来源: 火星时代
  9. tp5 生成二维码并与背景图合并
  10. 景格虚拟教具混合动力汽车动力系统虚拟结构原理展示台复制狗
  11. tortoise-orm 分页码(python)
  12. 小米笔记本电脑的SN码如何查找?
  13. window自带的桌面整理工具
  14. EditText的getText()方法
  15. 对捕金猎人的买涨买跌交易感悟?(交易基础篇)
  16. 利用Matlab将任意曲线旋转任意角度
  17. MySQL大表优化方案(推荐一)
  18. 小坤二次元导航HTML源码 很好看的引导页
  19. 求频率的公式是什么计算机,cpu时钟频率计算公式_CPU频率计算方法详解
  20. 房产中介租房平台小程序v4.1.61+ 前端(模块版)安装教程

热门文章

  1. Python Cerberus
  2. 网络技术-ENSP 华为模拟器(二)静态路由配置-3路由3PC
  3. SPP-net中的spatial pyramid pooling
  4. [2018-5-4]BNUZ你们还差得远呢
  5. 操作系统课后答案 第四--六章 黑新宏 胡元义主编
  6. python实现自适应分辨率截取桌面图片并识别图片文字
  7. 武汉新时标文化传媒有限公司喜欢看短视频而不是文章?
  8. 多语言的测试注意事项
  9. ENVI中计算两个甚至多个遥感影像的相关系数
  10. 【计算机组成与结构】主存储器