C语言常见的内存错误总结

    作者:朱克锋

    对c语言的程序员来说,管理和使用内存可能是个困难的、容易出错的任务.与存储器有关的错误属于那些最令人惊恐的错误,因为他们经在运行的时间和空间上都在距错误根源很远的地方才表现出来,程序在最终失败之前已经运行了很长时间,而且程序终止的位置距离出错的位置已经很远了。所以这类错误很难被查出来,也很难发现。但是对于常见的内存错误如果很熟悉的话就会更早的发现与避免错误的发生。下面我总结了常见的与内存有关的错误:

  内存泄漏

   内存泄漏通常是缓慢的隐形的,当我们不小心忘记释放我们分配的内存就会发生这样的问题,看下面的这段代码:
/*mem_leak*/
void mem_leak(int n)
{
 int *gb_n = (int *)malloc(sizeof(int) * n);
 //gb_n
在这里没有被释放,也没有被引用
 return ;
}
  
这段代码分配了一个内存快,在释放之前程序就返回了,如果这是一个服务器或守护进程的程序就很糟糕了,它会造成内存空间被占满的情况,一般现象是程序运行变慢最终终止。

   间接引用不良指针

    间接引用不良指针是一个常见的错误,其中最为经典的应该是scanf错误了,假设我们想要是用scanfstdin读取一个整数到一个变量,正确的方法应该是传递给scanf一个格式串和变量的地址:
void scanf()
{
 scanf("%d",$val); 
}
  
然而有些程序员特别是初学者,很容易出现以下错误:
/*scanf_bug*/
void scanf_bug()
{
 scanf("%d",val);
}
  
传递的是val的内容而不是他的地址,在这种情况下,scanf把变量的内容解释为一个地址,试图将一个字写到这个位置,最想的情况是程序立即终止,而在糟糕的情况下阿拉的内容被写到对应的内存中某个合法的读写区域,于是就会覆盖原来地址中的值,通常这样会造成灾难性的或令人困惑的后果.

    读未出世化的地址或存储器

    虽然.bss存储器的位置如未初始化的变量总是被初始化为零,但是对于堆来说就不是这样了,这种错误中最常见的是认为堆被初始化为零了:
/*uninit*/
int uninit_bug(int **array, int *p, int num)
{
 int i = 0;
 int j = 0;
 int *temp_p = (int *)malloc(sizeof(int) * num);
 for(i = 0; i < num; i++)
 {
  //temp_p[i] = NULL;
  for(j = 0; j  > num; j++)
  {
   temp_p[i] = array[i][j] * p[j];
  }
  return temp_p;
 }
}
  
上面的这短代码中显示的是:
不正确地认为指针temp_p被初始化为零,正确的应该在程序中把
temp_p
i]设置为零:
temp_p[i] = NULL;
或者使用另一个方法calloc来动态分配内存
:
int *temp_p = (int *)calloc(1, sizeof(int) * num);
方法calloc来动态分配内存时自动的将指针temp_p被初始化为零.

    错误的认为指针和它们指向的对象是相同大小的

    这种错误是错误的认为错误的认为指针和它们指向的对象是相同大小的:
/*pioter_to_obj_size*/
int **poiter_to_obj_size(int num1,  int num2)
{
 int i = 0;
 int **p_array = (int **)malloc(sizeof(int) * num1);
 //
 *p_array = NULL;
 for(i = 0; i < num1; i++)
 {  
  p_array[i] = (int *)malloc(sizeof(int) * num2);
 }
 return p_array;
}
   
现在我们来分析上面这段代码,这段代码的目的是创建一个由num1个指针组成的数组,每个指针都指向一个包含num2int类型的数组
.
   
在程序中我们把
:
    int **p_array = (int **)malloc(sizeof(int*) * num1);
   
写成了
:
    int **p_array = (int **)malloc(sizeof(int) * num1);
   
这时程序实际上创建的是一个int类型的数组,这段代码会在int和指向int的指针大小相同的机器上运行良好,如果把这段代码放在int和指向int的指针大小不同的机器上运行就会出现一些令人困惑的奇怪的错误.

错位错误

     这种错误是一种很常见的覆盖错误看下面的这段代码:
/*off_by_one*/
int **off_by_one(int num 1, int num2)
{
 int i = 0;
 int **p_array = (int **)malloc(sizeof(int*) * num1);
 *p_array = NULL;
 //for(i = 0; i < num1; i++)
 for(i = 0; i <= num1; i++)
 {
  p_array[i] = (int *)malloc(sizeof(int) * num2);
 }
 return p_array;
}
   
这段代码创建了一个num1个元素的指针数组,但是后面的代码却试图初始化数组的num1+1个元素,这样最后一个就会覆盖数组的后面的某个地址中的数据.

    错误地引用指针而不是它所指的对象

    错误地引用指针而不是它所指的对象,这种错误一般是由于C操作符的优先级和结合性引起的,这时我们会错误地操作指针而不是他所指向的对象先看一下下面的这个小的程序代码:
/*use_another_obj*/
int *use_another_obj(int **binheap, int *size)
{
 int *pac = binheap[0];
 binheap[0] = binheap[*size - 1];
 *size--; 
 heapify(binheap, *size, 0);

 return pac;
}
   
我本意是减少size指针指向的整数的值,却因为运算符的优先级出现了减少指针自己的值!正确的写法应该是这样的:

    binheap[0] = binheap[*size - 1];
    (*size)--;

    误解了指针运算

    首先看一下代码:
/*wrong_poiter_op*/
int *wrong_poiter_op(int *poiter, int num)
{
 while(*poiter && * != num)
 {
  poiter += sizeof(int);
 }

 return poiter;
}
   
我想用这个程序遍历一个int类型的数组,返回一个指针指向num的首次出现,但是结果却不是我期望的那样,因为每次循环时poiter += sizeof(int);都把指针加上了4,这样代码就遍历了数组中的每4个整数了,正确的应该是这样的:

poiter++ += sizeof(int);

    允许栈缓冲区溢出

    假设一个程序不见查输入串的大小就写入栈的目标缓冲区,那么这个程序就会有缓冲区溢出的错误:
/*bufoverflow*/
void bufoverflow_bug()
{
 char buf[256];
 gets(buf);

}
  
上面的这段程序代码就出现了缓冲区错误,因为gets函数拷贝一个任意长度的串到缓冲区了,所以我们必须使用fgets函数:

fgets(buf);
因为这个函数限制了输入串的大小.

    引用已经释放掉的堆中的数据

    引用已经释放掉的堆中的数据,看下面的代码:
/*ref_freeed_heap_data*/
int *ref_freeed_heap_data(int n, int m)
{
 int i = 0;
        int *x = NULL;
 int *y = NULL;
 x = (int *)malloc(sizeof(int) * n);
 free(x); 
 y = (int *)malloc(sizeof(int) * m);
 for(i = 0; i < m; i++)
 {
  y[i] = x[i]++;
  //x freeed!!!!!!
 }
 return y;
}
   
程序引用了已经释放的数据:free(x);

    引用不存在的变量

   下面的这个函数返回的是一个地址,指向栈里的一个局部变量然后弹出栈帧
/*ref_no_val*/
int *ref_no_val()
{
 int num;
 return &num;
}
这里他已经不再指向一个合法的变量了.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言常见错误小结 2008-06-12 10:55:08 C语言的最大特点是:功能强、使用方便灵活。C编译的程序对语法检查并不象其它高级语言那么严格,这就给编程人员留下 “灵活的余地”,但还是由于这个灵活给程序的调试带来了许多不便,尤其对初学C语言的人来说,经常会出一些连自己都不知道错在哪里的错误。看着有错的程 序,不知该如何改起,本人通过对C的学习,积累了一些C编程时常犯的错误,写给各位学员以供参考。 1.书写标识符时,忽略了大小写字母的区别。 main() { int a=5; printf("%d",A); } 编译程序把a和A认为是两个不同的变量名,而显示出错信息。C认为大写字母和小写字母是两个不同的字符。习惯上,符号常量名用大写,变量名用小写表示,以增加可读性。 2.忽略了变量的类型,进行了不合法的运算。 main() { float a,b; printf("%d",a%b); } %是求余运算,得到a/b的整余数。整型变量a和b可以进行求余运算,而实型变量则不允许进行“求余”运算。 3.将字符常量与字符串常量混淆。 char c; c="a"; 在这里就混淆了字符常量与字符串常量,字符常量是由一对单引号括起来的单个字符,字符串常量是一对双引号括起来的字符序列。C规定以“\”作字符串结束标志,它是由系统自动加上的,所以字符串“a”实际上包含两个字符:‘a'和‘\',而把它赋给一个字符变量是不行的。 4.忽略了“=”与“==”的区别。 在许多高级语言中,用“=”符号作为关系运算符“等于”。如在BASIC程序中可以写 if (a=3) then … 但C语言中,“=”是赋值运算符,“==”是关系运算符。如: if (a==3) a=b; 前者是进行比较,a是否和3相等,后者表示如果a和3相等,把b值赋给a。由于习惯问题,初学者往往会犯这样的错误。 5.忘记加分号。 分号是C语句中不可缺少的一部分,语句末尾必须有分号。 a=1 b=2 编译时,编译程序在“a=1”后面没发现分号,就把下一行“b=2”也作为上一行语句的一部分,这就会出现语法错误。改错时,有时在被指出有错的一行中未发现错误,就需要看一下上一行是否漏掉了分号。 { z=x+y; t=z/100; printf("%f",t); } 对于复合语句来说,最后一个语句中最后的分号不能忽略不写(这是和PASCAL不同的)。 6.多加分号。 对于一个复合语句,如: { z=x+y; t=z/100; printf("%f",t); }; 复合语句的花括号后不应再加分号,否则将会画蛇添足。 又如: if (a%3==0); I++; 本是如果3整除a,则I加1。但由于if (a%3==0)后多加了分号,则if语句到此结束,程序将执行I++语句,不论3是否整除a,I都将自动加1。 再如: for (I=0;I<5;I++); {scanf("%d",&x); printf("%d",x);} 本意是先后输入5个数,每输入一个数后再将它输出。由于for()后多加了一个分号,使循环体变为空语句,此时只能输入一个数并输出它。 7.输入变量时忘记加地址运算符“&”。 int a,b; scanf("%d%d",a,b); 这是不合法的。Scanf函数的作用是:按照a、b在内存的地址将a、b的值存进去。“&a”指a在内存中的地址。 8.输入数据的方式与要求不符。 ①scanf("%d%d",&a,&b); 输入时,不能用逗号作两个数据间的分隔符,如下面输入不合法: 3,4 输入数据时,在两个数据之间以一个或多个空格间隔,也可用回车键,跳格键tab。 ②scanf("%d,%d",&a,&b); C规定:如果在“格式控制”字符串中除了格式说明以外还有其它字符,则在输入数据时应输入与这些字符相同的字符。下面输入是合法的: 3,4 此时不用逗号而用空格或其它字符是不对的。 3 4 3:4 又如: scanf("a=%d,b=%d",&a,&b); 输入应如以下形式: a=3,b=4 9.输入字符的格式与要求不一致。 在用“%c”格式输入字符时,“空格字符”和“转义字符”都作为有效字符输入。 scanf("%c%c%c",&c1,&c2,&c3); 如输入a b c 字符“a”送给c1,字符“ ”送给c2,字符“b”送给c3,因为%c只要求读入一个字符,后面不需要用空格作为两个字符的间隔。 10.输入输出的数据类型与所用格式说明符不一致。 例如,a已定义为整型,b定义为实型 a=3;b=4.5; printf("%f%d\n",a,b); 编译时不给出出错信息,但运行结果将与原意不符。这种错误尤其需要注意。 11.输入数据时,企图规定精度。 scanf("%7.2f",&a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值