一、C语言中的内存管理 C语言程序在编译后需要载入内存中才能开始运行。内存中对于数据的划分不是随机的,而是根据这个数据的性质分段进行划分的。某段内存区域只会存储相应的数据。 具体来说,C语言对于内存空间的划分可以分为以下几个区域: 1)代码区:这段区域主要用来存储编译后的函数体的二进制代码,以及会用到的字符串常量。该区域是只读的。 2)数据区:这个区域主要存储已初始化的全局变量、静态变量、一般常量。 3)BSS区:这个区域主要存储未初始化的全局变量、静态变量。 4)堆区:由程序员手动申请、手动释放回收。若程序员不手动释放,则在程序结束后由操作系统回收。所对应的函数是malloc()、calloc()、free()等。 5)栈区:由系统自动分配、自动释放回收,存放函数的参数值、局部变量等。 6)命令行参数区:存放环境变量等,例如main()函数的传递的参数值。 其中,堆区与栈区的内存是在程序执行时由系统分配的。当该程序需要分配内存时才会分配,不需要时不会分配(或者分配后直接回收)。而BSS区、数据区、代码区是在程序执行开始阶段就由编译器分配内存,这三个区域的内存在程序运行时会一直存在,不会被临时回收。 示例:程序中列举了常见的数据及数据所在的存储区域。 #include #include #include int a=0;//全局变量初始化区 char *p1;//全局变量未初始化区 int main() { int b;//栈区 char s[]="123456";//s在栈区 char *p2;//栈区 char *p3="123456";//p3在栈区,"123456"在代码区 static int c = 0;//data区 p1=(char*)malloc(10);//堆区 p2=(char*)malloc(10);//堆区 printf("a address is %p\n",&a); printf("p1 address is %p\n",&p1); printf("b address is %p\n",&b); printf("s address is %p\n",s); printf("p2 address is %p\n",&p2); printf("123456 address is %p\n",p3); printf("123456 address is %p\n",&"123456"); printf("c address is %p\n",&c); printf("malloc1 address is %p\n",p1); printf("malloc2 address is %p\n",p2); return 0; } 二、野指针与空指针 1、野指针 指针变量在定义的时候,若未初始化或指向某对象,则该指针所存的地址值并不是空(NULL),而是一个随机值。这时这个指针指向一段未知内存,这样的指针就称为“野指针”。 野指针产生的原因主要有两个: 1.定义指针变量时未初始化或之后未指向正确的对象 2.指针释放后指针未及时置空 示例: #include int main() { int *p; *p = 100;//p此时是野指针,对野指针进行操作,非法 return 0; } 虽然程序编译没有问题,但运行时会报错:Segmentation fault (core dumped) Segmentation fault (core dumped):中文翻译为“段错误”。发生段错误的原因是程序对内存进行了非法操作,系统为了保护内存而强制终止了程序的运行。 发生段错误的原因很多,主要有:数组越界、修改只读内存、操作未知内存(操作野指针)等。 野指针无法通过语法检查而查出错误,只能通过编程人员的自身素养规避。初学者要十分小心使用指针变量,尤其不要出现野指针。 2、空指针 在C语言中,有宏定义“NULL”代表“空”。若指针的值为NULL,则表示该指针未指向任何内存。 int *p = NULL;//指针置空 初学者要养成良好习惯:若定义指针后暂时不指向任何对象,应该在定义时就把该指针置空防止出现野指针。 我们可以通过判断语句来判断一个指针是否是空指针: if(p == NULL) { ……//对指针p进行操作 } 三、返回指针值的函数(指针函数) 一个函数既可以返回int类型、char类型等,也可以返回一个指针类型数据,即返回一个地址。返回指针值的函数,称为“指针函数”。 指针函数的定义形式为: 类型名 *函数名(参数列表) 例如: int *a(int x,int y) char *strcpy(char *s1,const char *s2) 示例:有3个学生,每个学生有4门课成绩,存放在一个二维数组中。要求用户输入学生的序号(1或2或3)之后,能够输出这个学生的4门成绩。使用指针函数实现。 #include int *search(int(*p)[4],int n) { return *(p+n);//注意返回的是int*型而不是int型 } int main() { int score[3][4]={{60,70,80,90},{56,89,67,82},{78,90,66,100}}; int n,i; printf("查询第几个学生的成绩(1~3)?\n"); scanf("%d",&n); if(n!=1 && n!=2 && n!=3) { printf("输入错误!\n"); return 0; } n--; int *a = search(score,n); for(i=0;i<4;i++) { printf("%d ",a[i]); } printf("\n"); return 0; } 注意:当指针作为函数的返回值的时候,主调函数需要考虑指针指向的数据是否已经被回收。由于被调函数的局部变量存储在栈区,因此当被调函数执行完毕后系统会回收该段内存空间,这样被调函数内数据就会丢失。例如: char *mem() { char p[]="Hello World"; return p;//返回字符数组首地址 } int main() { char *str=mem(); printf("%s\n",str); return 0; } 运行程序会发现我们不会得到"Hello World"而是会输出乱码。 思考:怎样修改被调函数,使得主调函数可以输出"Hello World"? 四、指向函数的指针(函数指针) 在程序中定义的函数在编译时系统也会对函数代码分配一段存储空间,这段存储空间的起始地址(又称“入口地址”)称为这个函数的指针。 那么我们就可以定义一个指向这个函数入口地址的指针变量用于存储这个函数的入口地址,就意味着该指针指向该函数。 1、定义函数指针 函数指针的定义形式为: <类型名> (*<指针变量名>)(参数列表)//参数列表只写形参类型即可,不必写形参名 注意:函数指针与指针函数的定义主要区别就在于是否用()把函数名括起来,请注意区分。 例如: int (*p)(int,int); 这个指针变量p的类型是int (*)(int,int),表示这是一个指向有两个int型参数、返回值为int型的函数的指针。 2、使用函数指针调用函数 示例:写一个函数,求出2个数中较大值。调用函数用函数指针实现。 #include int max(int x,int y) { return x>y?x:y; } int main() { int (*p)(int,int); p = max; int a,b; printf("请输入2个整数:"); scanf("%d%d",&a,&b); printf("较大值是%d\n",(*p)(a,b)); return 0; } 注意: 1.定义了一个函数指针,但不代表该函数指针可以指向任何类型的函数。示例程序中的指针p只能指向参数是2个int型、返回值是int型的函数。不能指向非这种类型的函数。 2.使用函数指针调用函数前,一定要先将该指针变量指向该函数。 3.在给函数指针赋值时,只需函数名即可,千万不要写成函数调用。 p = max;//正确 p = max(a,b);//错误 4.使用函数指针时,只需用*<指针变量名>来代替普通函数调用即可。 5.函数指针不能进行算数运算。如p+1、p-1、p++、p--等都是非法的。 练习:写两个函数,一个求2个数中较大值,另一个求2个数中较小值。用户选择1或2,输入1代表调用较大值函数,输入2代表调用较小值函数。调用函数用函数指针实现。 答案: #include int max(int x,int y) { return x>y?x:y; } int min(int x,int y) { return x *<指针变量名> 例如const char *src; 说明:const在数据类型前,表示限制该指针指向的对象的内容不可修改。这样可以保护指针指向的数据。 在C库函数中,涉及到数组等操作(尤其在字符串操作函数中)常把形参设为const类型以保护数据。如: char *strcpy(char *s1,const char *s2); strcpy()函数意为将字符数组或字符串s2的内容复制到字符数组s1中。s2指向的对象需要数据保护(即不可修改),因此使用const关键字加以保护。 2、<数据类型> *const <指针变量名> 例如int *const a; 说明:const在数据类型名后、指针变量名前使用,表示限制该指针指向的位置唯一,即不可修改该指针指向的对象。但仍可以使用*<指针变量名>来修改指向的对象的内容。 3、const <数据类型> *const <指针变量名> 例如const int *const a; const既在前又在后,表示既不可修改指针的值(即不可修改该指针指向的对象),也不可修改所指向对象的内容。 六、指针数组和多级指针 1、指针数组 如果有一个数组其所有元素均为指针类型,则这个数组称为“指针数组”。也就是说,指针数组的每个元素都是一个指针变量。 1)定义一个指针数组 定义一维指针数组的一般形式为 <类型名> *<数组名>[数组长度] 例如:int *p[4]; 注意不要写成int (*p)[4]; 2)指针数组的应用 示例: #include #include void print_string(char *name[],int n) { int i; for(i=0;i #include #include//malloc函数头文件 #define MAX 10 int *get_memory(unsigned int a) { int *p = (int*)malloc(a); if(p==NULL)//申请失败 { printf("申请空间失败"); return NULL; } return p; } int main() { int *a = get_memory(MAX*sizeof(int)); if(!a) { printf("申请空间失败!\n"); return 0; } int i; for(i=0;i