C语言内存错误常见问题分析

一个迷惑的异常

//之前帮同事调试一段代码的时候,出现了一个让人迷惑的错误:同样的代码,不同的板卡上出现不同情况
//1:使用板卡1刷卡启动充电,刷卡停止,正常
//2:使用板卡1刷卡启动,触发急停停止后,异常,计费线程死机了
//3:使用板卡2,刷卡启动,急停停止,也正常

//常规分析,认为可能是计费线程修改了,导致线程进入死循环,而无法给线程喂狗,于是查找重载的几个计费接口,修改后没有改善
//分析,直流桩无异常,一体化有异常,是否一体化充电控制接口有异常————查看并无明显异常
//最后分析问题如下:
BYTE AxjGetStopChgReason(BYTE bpn)
{
	BYTE bSotpReason;
	BYTE ReasonNum = 52;
	ReadItemEx(BN0, bpn, 0xB885, &bSotpReason); 
    for (BYTE i = 0; i < ReasonNum; i++)
	{
		if (g_tStopReason[i].bClou == bSotpReason)
			return g_tStopReason[i].bAXJ;
	}
    return 0;
}
//因为,g_tStopReason在持续的修改过程中,最终并没有52个了(有些屏蔽了),导致访问到超出g_tStopReason数组外的地方————越界了
//修改为:
BYTE ReasonNum = sizeof(g_tStopReason) / sizeof(g_tStopReason[0]);

//那问题来了,一般来说,越界不是会挂掉吗?为啥这里还正常运行,直流可以而交流不行??
//下面的几点将从原理及现场说明,如果发生越界,可能的现象及后果有哪些

C中常见的与内存有关的错误

内存错误可能的后果

可能立刻报段错误,也可能保护故障,也可能长时间不出错(只是运行结果不对),甚至还有可能没有影响,程序也正常运行,那是什么原理呢?
如下一段为通用的内存管理模式:
内核虚拟内存 进程相关的内核数据结构
如页表、task、mmu结构,内核栈等
每个进程一样 物理内存
内核代码和数据
用户栈
共享库映射区 共享库如libc.so,每个库包含.data和.text区域
brk开始 运行时malloc分配的堆
未初始化的数据 .bss
已初始化的数据.data
0x400000开始 代码段.text
如果你的内存出错了,具体出错位置:
1:地址定位到其他进程了,地址不是本进程的——segment error
2:地址定位到内核去了————内存保护错误
3:地址定位错了,但还在本进程中,只是指向了别的有用地址————这个后果不唯一,可能的后果包括:
	a:引用的地址是本进程的一个堆空间的,那就会改变堆空间的地址的数据(如果应用指针有修改的话)
	b:引用的地址是本进程的一个栈空间,修改了栈空间的内容,
	但无论怎样,引用内存不会有致命问题(顶多引用数据不正确),修改内存可能会导致:
	a:后续真正该内存的主人过来用的时候,引用地址错了,导致segment error或者其他逻辑错误
	b:修改了地址的内容,后续程序运行过来的时候,数据被修改了,运行逻辑错误。

错误——间接引用坏指针

正常:scanf("%d", &val);
错误:scanf("%d", val);————此时scanf将val解释为一个地址,
最好情况下,程序立即异常终止(内存到了别的内存区),最糟糕情况下,val对应虚拟内存某个合法读/写区域,于是覆盖了这个内存,以此通常会运行相当长一段时间后造成灾难性、令人困惑的后果。

错误——读未初始化的内存

.bss内存一般被加载器初始化为0,但堆内存不是,常见错误就是假设对内存初始化为0.
int *testvec(int **ptra, int *x, int n)
{
    int i,j;
    int *ptrb = (int *)malloc(n * sizeof(int));
    
    for (i=0; i<n; i++)
    {
        for (j=0; j<n; j++)
            ptrb[i] += ptra[i][j] * x[j];
    }
    return y;
}
此事例中,不应该假设ptrb被初始化为0,应该显式初始化为0.

错误——允许栈缓冲区溢出

void bufoverflow()
{
	char buf[32];
    
    gets(buf);
    return ;
}
//此处,gets获得数据可能超过32字节溢出,应该改用限制输出长度的接口,如fgets

错误——价值指针和他们指向对象大小相同

//创建一个n个指针组成数组,每个指针指向一个包含m个int的数组
int **makArray1(int n, int m)
{
    int i;
    int **A = (int **)malloc(n * sizeof(int));//(n * sizeof(int *))
    
    for (i=0; i<n; i++)
        A[i] = (int *)malloc(m * sizeof(int));
    
    return A;
}
//int **A = (int **)malloc(n * sizeof(int));实际创建的是int数组,在int和指向int的指针大小相同时运行正常,如果64位机器就结果异常了
//A[i]超出了,但可能超出部分是一个分配块边界,所以可以正常运行,之后程序运行很久后释放块时,分配器(alloc)合并空间失败,没有明显原因,可能提示“action at distance” (在远处起作用)

错误——造成错位

int **makArray2(int n, int m)
{
    int i;
    int **A = (int **)malloc(n * sizeof(int *));
    
    for (i=0; i<=n; i++)
        A[i] = (int *)malloc(m * sizeof(int));
    
    return A;
}
//A[i]初始化试图初始化n+1个元素

错误——引用指针,而不是指针指向的对象

int *binheapdel(int **binheap, int *size)
{
    int *packet = binheap[0];
    
    binheap[0] = binheap[*size -1];
    *size--;	//符号优先级,(*size)--
    heapify(binheap, *size, 0);
    return (packet);
}
//此处,*和--优先级一样,从右向左结合,所以*size--是减少指针自己值,而不是指向整数的值
//幸运的话程序立即失败,更可能发生的是:程序运行很久才有一个不对的结果,此时检查无从查起。

错误——误解指针运算

//忘记了指针的算术运算是以它指向的对象的大小为单位进行的,单位不一定是字节
int *search(int *p, int val)
{
    while(*p && *p!=val)
        p += sizeof(int);	//p++
    
    return p;
}
//每次循环,P越过4个int,实际结果不正确。

错误——引用不存在的位置

int *getptr()
{
    int val=100;
    
    return &val;
}
//返回的指针指向栈的局部变量,然后弹出栈帧,此时指针还是一个合法地址,但不是合法变量了,后续该帧帧被其他函数使用,此处返回的值如被修改就可能导致莫名修改,轻则异常,潜在灾难性后果啊

错误——引用空闲栈块中的数据

int *heaptest(int n, int m)
{
    int i;
    int *x,*y;
    
    x = (int *)malloc(n * sizeof(int));
    ......
    free(x);
    
    y = (int *)malloc(m * sizeof(int));
    for (i = 0; i < m; i++)
        y[i] = x[i]++;		//x已经释放了
    
    return y;
}
//释放后,x[i]可能是某个其他函数申请的堆空间(当然如果内存很小,也可能是某个函数用的一个大的栈),x位置被重写了,此错误会在程序执行后面出现。

错误——引起内存泄漏

void leak(int n)
{
    int *x = (int *)malloc(n * sizeof(int));
    return ;
}
//内存泄漏是缓慢、隐形的杀手

你可能感兴趣的:(C/C++编程,c语言,c++)