C语言和内存打交道比较多的莫过于指针了.
1.野指针:
为防止野指针的出现,需要养成良好的编程习惯--指针要时刻要有明确的指向对象,否则其随机指向一块内存区域,哪个地址再引用就会引发莫名的BUG.为避免野指针,规范的编程习惯如下:
定义指针时初始化为NULL;指针用完后还是赋值为NULL.
2.内存的三个部分
内存可以分为三个部分:静态区、栈、堆.
2-1.静态区:保存自动全局变量和static变量(包括static全局和局部变量).静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配.
2-2.栈:保存局部变量.栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁.其特点是效率高,但空间大小有限.
2-3.堆:由malloc系列函数或new操作符分配的内存.其生命周期由free或delete决定.在没有释放之前一直存在,直到程序结束.其特点是使用灵活,空间比较大,但是容易出错.
3.常见的内存错误及对策.
3-1.指针没有指向一块合法的内存.
主要是指指针没有初始化.如下面这个示例:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct __student
{
char *name;
int age;
}student,*pstudent;
int main(int argc,char **argv)
{
student stu;
strcpy(stu.name,"Se7en");
stu.age = 24;
printf("stu.name = %s\n",stu.name);
printf("stu.age = %d\n",stu.age);
return 0;
}
输出结果:
root@seven-laptop:~/learn/C_Program# ./initpoint
Segmentation fault
root@seven-laptop:~/learn/C_Program# vim initpoint.c
可见报段错误了.原因分析如下:
1).我们在定义stu这个结构体变量时,只为这个结构体内部name成员本身分配了4个字节,它并不一定指向一块合法的地址,它内部存放的只是一些乱码;
2).调用strcpy往这个乱码地址存放数据是危险的,因为很有可能这块地址是它无权访问的.
因此,当我们操作一个指针时.一定要明确这个指针指向哪!!!!
将上述代码达到我们预期的输出结果,可以通过下面两种方式实现.
方式一:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct __student
{
char *name;
int age;
}student,*pstudent;
int main(int argc,char **argv)
{
student stu;
// strcpy(stu.name,"Se7en");
stu.name = "Se7en";
stu.age = 24;
printf("stu.name = %s\n",stu.name);
printf("stu.age = %d\n",stu.age);
return 0;
}
输出结果:
root@seven-laptop:~/learn/C_Program# ./initpoint
stu.name = Se7en
stu.age = 24
root@seven-laptop:~/learn/C_Program# vim initpoint.c
分析:这时stu.name是指向"Se7en"这个字符串的.编译器为这个字符串分配的地址肯定是合法的.
方式二:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct __student
{
char *name;
int age;
}student,*pstudent;
int main(int argc,char **argv)
{
student stu;
stu.name = (char *)malloc(sizeof(char) * 50);
strcpy(stu.name,"Se7en");
stu.age = 24;
printf("stu.name = %s\n",stu.name);
printf("stu.age = %d\n",stu.age);
free(stu.name);
return 0;
}
输出结果:
root@seven-laptop:~/learn/C_Program# ./initpoint
stu.name = Se7en
stu.age = 24
root@seven-laptop:~/learn/C_Program# vim initpoint.c
上述两种修改方式都是保证了我们要访问的stu.name指针有明确的指向.
3-2.assert()宏
被调用的函数经常参数是指针类型的.很多代码中都可以看到其使用assert()宏来判断传入的参数的合法性.assert()宏的意义如下:
assert()括号内的内容为假则终止程序并提示出错;为真则继续运行后面的代码.
因此,我们常用如下形式来判定传入的指针参数的合法性.
assert(NULL != p);
3-3.小心strlen()函数
我们常用strlen()来计算一个字符串的长度,strlen()计算出来的长度是不包括"\0"的.因此需要分配多一个字节.示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
char *pStr = "HelloWorld!";
char *q = NULL;
printf("strlen(pStr) = %d\n",strlen(pStr));
q = (char *)malloc(sizeof(char) * strlen(pStr));
strcpy(q,pStr);
printf("strlen(q) = %d\n",strlen(q));
printf("%s\n",pStr);
printf("%s\n",q);
free(q);
q = NULL;
return 0;
}
上述代码在GCC 4.2.2可以正常输出:
root@seven-laptop:~/learn/C_Program# ./malloc
strlen(pStr) = 11
strlen(q) = 11
HelloWorld!
HelloWorld!
root@seven-laptop:~/learn/C_Program# vim malloc.c
但是在VC6.0下运行报错.要在VC6.0正常运行并兼容GCC.代码修改如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
char *pStr = "HelloWorld!";
char *q = NULL;
printf("strlen(pStr) = %d\n",strlen(pStr));
q = (char *)malloc(sizeof(char) * strlen(pStr) + 1 * sizeof(char));
strcpy(q,pStr);
printf("strlen(q) = %d\n",strlen(q));
printf("%s\n",pStr);
printf("%s\n",q);
free(q);
q = NULL;
return 0;
}
输出结果:
root@seven-laptop:~/learn/C_Program# ./malloc
strlen(pStr) = 11
strlen(q) = 11
HelloWorld!
HelloWorld!
root@seven-laptop:~/learn/C_Program# vim malloc.c
可见,GCC对代码前后的输出结果是一样的.
总而言之,如果要存放一个字符串,记得多分配一个字节就是了.
3-4.malloc()函数注意检查返回值.
malloc()函数意为从堆上分配出一块内存以供程序使用,但是内存分配有可能是失败的.因此,一定要检查其返回值.如下:
char *p = (char )malloc(1024);
if(NULL == p)
{
//内存申请失败处理.
}
3-5.free()之后的故事
free()函数就是把这块内存和p之间的所有关系断开.它要和malloc必须一一对应.这时候特别注意的是,指针变量p本身保存的地址并没有改变,我们需要重新把p的值变为NULL.如下:
free(p);
p = NULL;