C语言中常见的内存错误
14 Apr 2017 | categories academic如果程序在访问内存时发生错误,可能会导致segmentation fault(段错误,简称为SIGSEGV)。
- SIGSEGV 是一个用户态的概念,是操作系统在用户态程序错误访问内存时所做出的处理
- 当用户态程序访问(访问表示读、写或执行)不允许访问的内存时,产生SIGSEGV。
- 当用户态程序以错误的方式访问允许访问的内存时,产生SIGSEGV。
当用户态程序访问一个会引发 SIGSEGV 的地址时,硬件首先产生一个 page fault,即“缺页异常”。在内核的 page fault 处理函数:
- 首先判断该地址是否属于用户态程序的地址空间。如果该地址属于用户态地址空间,检查访问的类型是否和该内存区域的类型是否匹配,不匹配,则发送SIGSEGV 信号
- 如果该地址不属于用户态地址空间,检查访问该地址的操作是否发生在用户态,如果是,发送 SIGSEGV 信号,否则产生内核错误。
常见的*(0)=0
空指针赋值导致的段错误,源于系统禁止为用户空间的第一个页分配物理内存,从而产生page fault。
下面例举常见的内存错误原因。
坏指针的解引用
如,
scanf("%d", &val)被错误地写成了, scanf("%d", val)
于是系统将val的值解释为地址,并尝试向其中写入信息。最好情况下,程序以异常而中止;最坏情况下,该地址可写,于是内存被覆盖了。
读取未初始化内存
未初始化的变量会放在bss段中清零,但为初始化的指针(堆上分配的内存)却不会。直接解引用可能会造成莫名其妙的错误。
允许栈溢出
典型的是不检查串的大小就写入栈中的目标缓冲区,如gets
函数对数组的越界访问。
栈溢出的后果可参见汇编基础的”缓冲区溢出”一节。
假设指针和它们指向的对象大小相同
指针大小和系统架构有关,如x86_32的指针大小是32位;而指针对象的大小则因对象类型而异。即sizeof(int)
= sizeof(int *)
并不一定成立。
“差一位”(Off-by-One)错误
假设sizeof(p)=n
,那么有效区间是[0,n),p[n]是无效的。
引用指针,而不是它指向的对象
此类错误常见于*size--
运算的误解。由于*和++/–的优先级相同,同时出现时遵循从右向左的运算规则,所以上述运算的含义是:指针减1,再解引用。
搞错指针运算
常见的误用是忘记指针运算的单位是其指向对象的大小,如对于int指针,p++
,意味着将地址加4,因为int指针长度为4字节。
引用不存在的变量
将局部变量的地址作为函数返回值是此类错误的常见形式。函数返回后,其所在栈空间会被释放并可能被别的函数所使用。此时,这个被当做函数返回值的指针指向的可能是别的函数的某个局部变量,虽然地址是合法的。
引用空闲堆块中的数据
常见形式是在free
后接着引用指针。那么,指针指向的可能是新分配给别的指针的内容。
引入内存泄漏
即忘记释放已经分配的块,使堆里充满了垃圾。对于守护进程或服务器来说,这是相当严重的问题,因为进程不会终止。