遇到了一个奇怪的问题,在for循环内,if判断指针指向的内容,for循环次数超越指针数组范围,没有报错!!!
C语言编译器是不会对数组下标进行越界检查的,于是重新回顾一下内存溢出和内存越界:
- 内存溢出:你要分配的内存超出了系统能给你的,系统不能满足需求,于是产生了溢出;
- 内存越界:你想系统申请一块内存,在使用的这块内存的时候,超过出了你申请的范围。
然后在内存越界的基础上,我这次又有了新的理解:
- 越界访问:仅仅越界读取内容,不一定会造成段错误(linux 越界读取,是不会出现段错误的),或者程序崩溃死机问题;这个真的是看运行环境!
- 越界修改:越界写入内容或者修改,一定会产生段错误 Segmentation fault (core dumped),导致程序崩溃死机。
先看一下内核版本: 是基于这个内核版本进行试验。
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$ uname -i
x86_64
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$ uname -a
Linux ubuntu140453200 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$
下面的例子是从项目代码拷贝出来的,操作是一样的,也是多循环了128次。
在ASR或者高通平台上面跑是没有问题的。但是问题以奇怪的方式显现出来,当打开串口抓取log时,程序就卡在for循环的判断,if(p[i] == ';') 判断分号这里,然后超过30秒被系统kill掉,返回ERROR。还有云端设置主动上报时,也会卡死在这里。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LENGTH 1024
int main(int argc, char *argv[])
{
int i = 0;
char *p = NULL;
char buf[MAX_LENGTH] = {0};
memset(buf, 0, sizeof(buf));
p = buf;
for(i=0; i < 256; i++)
{
p[i] = 0x35;
}
p[i] = '\0';
for(i = 0; i < (MAX_LENGTH+128); i++)
{
if(p[i] == ';')
{
printf("find ; index %d \n", i);
break;
}
if(i >= MAX_LENGTH)
{
printf(" index %d\n", i);
}
}
return 0;
}
运行结果:
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$./pointer_2
index 1024
index 1025
...
...
...
index 1150
index 1151
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$
需要加判断跳出for循环,程序的修改如下:
for(i = 0; i < (MAX_LENGTH+128); i++)
{
if(p[i] == ';')
{
printf("find ; index %d \n", i);
break;
}
if(i >= MAX_LENGTH)
{
printf(" index %d\n", i);
}
//这里加上判断是否到达字符串的结尾,到达结尾跳出循环,越界了又没有跳出循环,奇奇怪怪的问题就来了
if(p[i] == '\0'){
break;
}
}
越界访问与越界修改差异很大,于是我有突发奇想,试试memset,对数组进行越界尝试,动手写了一个程序,发现越界时也没有报错,程序还是执行下去了,没有在我期待的地方报错,出现 Aborted (core dumped) 或者Segmentation fault (core dumped) 或者 bus error 这样的错误。
有两个期待:
- 无论执行前面的memset还是后面的memset,都应该出现段错误Segmentation fault (core dumped),或者是总线错误;
- 注释掉第一个memset,如果是没有出现段错误,那么后面第二次打印与第一次打印应该有差异,理由是memset会对后面的堆栈空间进行修改。
结果这两个期待都没出现!!!
int a = -1;
char b[16]="123456";
//memset(b, 0,32); //注意这里越界访问了,只有16字节空间,却修改了32字节
int c = -1;
printf("a=%d,c=%d\n", a,c);
printf("a=%d,c=%d\n", a,c);
a = 1;
c = 2;
memset(b, 0,32); //注意这里越界访问了,只有16字节空间,却修改了32字节
printf("a=%d,c=%d\n", a,c);
printf("a=%d,c=%d\n", a,c);
运行结果:
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$./pointer_2
a=-1,c=-1
a=-1,c=-1
a=1,c=2
a=1,c=2
zhang@ubuntu140453200:/mnt/external/zhang/test/linux_c$
原因分析与调试:
- 在linux系统中分配内存空间并不是连续的,是存在了优化,这一点可以加 %p打印地址(结果证明不是连续的)
- 如果想取消这种优化,使用限制字符 volatile
- 编译时选用 -o0 参数,这样才能取消内部优化机制
- 使用gdb单步调试