day10 内存管理
@toc
一、变量作用域
全局变量和局部变量
一个C语言变量的作用域可以是代码块,作用域就是函数作用域或者文件作用域
代码块:{}之间的一段代码;
1.文件作用域:
如果一个变量在其他的代码文件中已经定义了,可以在本程序中使用,但是使用前要使用关键字:extern
例如:extern int age
//有一个int型变量age已经在其他文件中定义了,这里就直接使用了;
static int a = 0;
//在定义这个变量的文件内部是全局的,但在文件外部不可用
2.auto自动变量
C语言中所有的局部变量默认中都是auto,,所以auto
可以省略。不需要关注它在内存中什么时候消失和出现
auto int i = 0;
等效于int i = 0;
3.register变量
register int i = 0;
//建议,如果有寄存器空闲的话,就将这个变量放进寄存器中使用,可以加快读取
但是int *p = &i
//这个语句就会报错,因为放在寄存器中就没有内存地址了
4.动态变量和静态变量
- 动态变量
#include<stdio.h>
void myauto()
{
int a = 0;
printf("a = %d\n",a);
a++;
}
int main()
{
int i;
for(i = 0; i < 5; i++)
{
myatuo();
}
}
程序的输出结果为 :
a = 0
a = 0
a = 0
a = 0
a = 0
- 静态变量
首先只要整个程序开始执行之后,静态变量是一直存在的,不消失的;
其次,静态变量值初始化一次,即static int a = 0;
语句只执行一次
#include<s tdio.h>
void mystatic()
{
static int a = 0;//整个进程运行过程中一直有效,是在静态区,但是只能mystatic函数内部访问使用
printf("a = %d\n",a);
a++;
}
int main()
{
int i = 0;
for(i = 0; i < 5; i++)
{
mystatic();
}
}
程序运行结果为:
a = 0
a = 1
a = 2
a = 3
a = 4
5.变量和函数是否使用extern的区别
-
变量
- extern int age;//当age这个变量是在另一个.c文件中时,需要在别的文件中调用这个变量时候使用
- int age; //这包含两个含义:声明一个变量或者定义一个变量
-
函数(下面两个没有什么区别)
- extern void age()
- void age()
二、内存四区简介
- 1.代码区
存放可执行的代码;程序被操作系统加载到内存中时候,所有可执行的代码都加载到代码区,这块内存是不可以在运行期间修改的==;
- 2.静态区
存放所有的静态变量/常量和全局变量/常量
- 3.栈区
栈区是一种先进后出的内存结构,所有的自动变量、指针,数组、函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域的时候,自动从栈中弹出;
对于自动变量,什么时候入栈和出栈都是系统自动控制的
栈区的大小是可以动态设置的,不过不会特别大,一般大小为XX k,
C语言中函数的参数变量是从右往左入栈的,也就是最右边的参数是最先入栈的
栈溢出
当栈空间已满,但是还往栈内存中压变量,这就是栈溢出;
- 4.堆区
堆和栈一样,是一种在程序运行过程中可以随时修改的内存区域,但是没有栈那样的先进后出的顺序,是一个大容器,容量远远大于栈,堆内存空间的申请和释放需要手工通过代码来完成;
4.1.分配堆的方法:
#include<stdio.h>
#include<stdlib.h> //需要加一个头文件
int main()
{
int *p = malloc(sizeof(int)*10); //语句含义为:在堆中间申请内存,函数的返回值为无类型的指针,参数为size_t无符号的整数 ,现在这个语句的含义是:申请一个内存大小为10个int大小的空间。指针p指向分配出来的堆的地址;
memset(p ,0,sizeof(int) * 10); //将分配的这个空间置零初始化
free(p);//释放通过malloc分配的堆内存
return 0;
}
4.2.堆和栈的区别
#include<stdio.h>
#include<stdlib.h>
void *geta() //这是错误的,不能将一个栈变量的地址通过函数的返回值返回
{
int a = 0;
return &a;
}
void *geta1() //可以通过函数的返回值返回一个堆地址,但是一定要free
{
int *p = malloc(sizeof(int)); //申请了一个堆空间
return p; //返回值为p 即是分配的堆空间的地址
}
void *gata2() //这也是合法的,只要将main函数中的geta1改为geta2,然后将free(getp)去掉即可
{
static int a = 0; //使用static即使a为静态变量,存在于静态区,会一直存在不会被释放
return &a;
}
int main()
{
int *getp = geta1();
*getp = 100;
free(getp); //getp指向了划分的堆区域,因此释放即可
}
理解下面程序
这个程序本质上有错误,具体的修改见下面的一个程序
#include<stdio.h>
#include<stdlib.h>
void getheap(int *a)
{
a = malloc(sizeof(int) * 10);
}
int main()
{
int *p =NULL;
gatheap(p); //实参没有任何改变,相当于这里的p一直没有任何改变
p[0] = 1;
p[1] = 2;
printf("p[0] = %d ,p[1] = %d\n",p[0],p[1]);
free(p);
}
从这程序中可以看出:也就是说a的值改了,a本身是在栈里面的,它只是指向堆里面的一个空间地址;然后实参p的值一直没有变化
getheap在执行完之后,a就消失了,导致他指向的具体堆空间的地址编号也随之消失了;
修改
#include<stdio.h>
#include<stdlib.h>
void getheap(int **a)
{
*a = malloc(sizeof(int) * 10); //这里的*a就是指针p中的值了,
}
/*
int **a = &p;等价于
int **a;
a = &p
*/
int main()
{
int *p =NULL;
getheap(&p); //传递的是P的地址
p[0] = 1;
p[1] = 2;
printf("p[0] = %d ,p[1] = %d\n",p[0],p[1]);
free(p);
return 0;
}
程序的分析图片:
三、内存模型详解以及Linux系统堆内存大小的分析
1.栈和堆的比较
-
栈(stack)
-
明确知道数据占用多好内存
-
数据很少
-
变量离开作用范围后,栈上的数据会自动释放
-
栈的最大尺寸固定,超过则引起栈溢出
-
堆(heap)
- 需要大量内存的时候
- 不知道需要多少内存
//在堆中可以建立一个动态的数组
#include<stdio.h>
int main()
{
int i ;
scanf("%d",&i);
int *array = malloc(sizeof(int) * i);
free(array);
}
2.堆的分配和释放
内存的最小单位是字节,但是操作系统在管理内存的时候,最小的单位是内存页(32位操作系统中每一页为4k )
3.malloc和calloc和realloc
- malloc
#include<stdio.h>
#include<stdio.h>
int main()
{
char *p = malloc(10);//分配一个10个字节的空间,但是这10个空间没有清理过,所以每次使用可能都是10个随机数
memset(p,0,10); //这里含义是将p 中的10个元素全部置零
int i = 0;
for(i = 0; i < 10; i++)
{
printf("%d\n",p[i]);
}
free(p);
return 0;
}
- calloc
#include<stdio.h>
#include<stdlib.h>
#include<string.h >
int main()
{
char *p = calloc(10,sizeof(char));//分配10个大小为sizeof(char)的空间,并且自动全部置零
int i = 0;
for(i = 0; i < 10; i++)
{
printf("%d\n",p[i]);
}
return 0;
free(p);
}
- realloc
如果已分配的空间不够用,需要划分出一块新的内存空间,要求是这块内存空间和原来已划分的空间是连续的;
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char *p = calloc(10,sizeof(char));//这10个空间是置零了的
char *p1 = realloc(p,20);
//参数含义:需要扩展的空间名,需要扩展后的空间为20个字节
//语句含义:在原有内存基础之上,在堆中间增加连续的内存;如果原有的内存没有连续的空间可拓展,那么会分配一个空间,将原有的内存copy到新空间,然后释放
//新开辟的这10个字节空间是没有置零的
int i = 0;
for(i = 0; i < 20; i++)
{
printf("%d\n",p1[i]); //这里的p也需要改为p1
}
return 0;
free(p1); //这里释放的是p1
}
当要减少原来已划分的空间的时候
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char *p = calloc(10,sizeof(char));//这10个空间是置零了的
char *p1 = realloc(p,5);
int i = 0;
for(i = 0; i < 5; i++)
{
printf("%d\n",p1[i]); //这里的p也需要改为p1
}
return 0;
free(p1); //这里释放的是p1
}
当realloc
的第一个参数为空的时候,即:realloc (NULL,5)
等效于malloc(5)
4.二级指针分派堆空间
如果是通过一个函数的参数给实参分配堆空间内存,那么一定是二级指针的形式
正确模型1
#include<stdio.h>
void getheap1(int **p)
{
*p = malloc(100);
}
int main()
{
int *p = NULL;
getheap(&p);//如果是通过一个函数的参数给实参分配堆空间内存,肯定使用二级指针
free(p);
}
正确模型2
#include<stdio.h>
int *getheap2()
{
return = malloc(100);
}
int main()
{
int *p = NULL;
p = getheap2();
free(p);
}
正确模型3:
常量、静态变量、全局变量都在都在静态区域,一直是有效的
#include<stdio.h>
int *getstring2()
{
return = "hello";//可以将一个常量的地址作为函数返回值返回
}
int main()
{
const char *ss = getstring2();
printf("ss = %c\n",c);
return 0;
}
正确模型4
#include<stdio.h>
int *getstring4()
{
static char array[10] = "hello"; //仍然在静态区
return array;
}
int main()
{
const char *s = getstring4();
printf("s = %s\n",s);
return 0;
}
程序输出结果为:s = hello
错误的模型1
#include<stdio.h>
int *getstring()
{
char array[10] = "hello";
return array;
}
int main()
{
char *s = getstring();//当getarray()这个语句执行完之后,因为数组array是存放在栈中,地址就释放了
printf("s = %s\n",s);
return 0;
}
程序运行结果为乱码