C语言-09C语言内存管理

3 篇文章 0 订阅
1 篇文章 0 订阅

内存管理
作用域:
一个c语言变量的作用域可以是代码快{},函数,文件作用域。
代码块是{}之间的一段代码
出现在{}之外的变量,就是全局变量

#include<stdio.h>

int a=20;//全局变量,若main()函数中没有定义a,那么下面的a都是20.
void test01(){
    int a1=0;
    int a=100;
}
void test02(){
    int a2=0;
}

int main(int argc,char *args[]){
    int a=0;
    //a1=100;代码错误,a1的作用域在test01函数里面,外部不能使用
    a=10;
    {
        int a=100;//作用域在{}内,它是两个变量
        printf("%d\n",a);//a=100的,若上段代码没有的话,它就是等于10
    }
    printf("%d\n",a);//a=10的
    return 0;
}

ectern关键字的使用:
在这里插入图片描述

#include<stdio.h>

extern int age;//有一个变量,类型是int名字是age,已经在其他文件中定义了的这里直接使用它,是在任何括号之外的变量=全局变量
void set_age(int n){
    age=n;
}
void get_age(){
    printf("age=%d\n",age);
}
int main(int argc,char *args[]){
    set_age(age);
    get_age();
    return 0;
}

auto
auto关键字:自动变量,不需要关心它在内存当中什么时候出现什么时候消失
不写auto关键字,c语言默认都是auto的
不写的关键字还有signed


register
register变量:寄存器变量
一般情况下在内存当中,如果能把变量放在cpu的寄存器中,代码的执行效率就会更高。

#include<stdio.h>

int main(int argc,char *args[]){
    register int i=0;//建议如果有寄存器空闲,那么变量就放在寄存器里面使用
    //但是这里&i就无法使用,对于一个寄存器变量是不能取地址操作的
    return 0;
}

代码块作用域的静态变量:
静态变量是指内存位置在程序执行期间一直不变的变量,一个代码块内部的静态变量只能被这个代码块内访问。
在没有加上static的情况:

#include<stdio.h>

void mystatic(){
    int a=0;//被调用一直被初始化
    printf("a=%d\n",a);
    a++;
}
int main(){
    int i;
    for(i=0;i<10;i++){
        mystatic();
    }
}
a=0
a=0
a=0
a=0
a=0
a=0
a=0
a=0
a=0
a=0

Process finished with exit code 0

加上static的情况:每次调用都会发生变化

#include<stdio.h>

int c=0;//全局的
static int d=0;//在定义的这个变量的文件内是全局的,但在文件外部不可用
void mystatic(){
    static int a=0;
    //静态变量,值初始化一次而且整个程序运行期间静态变量一直存在。整个进程运行期间一直有效,是在静态区,但是只在Mystatic()这个函数内有效。
    //若是:int a=0,只执行一次。函数被第二次调用不再执行。每次是临时的,可”修改“
    printf("a=%d\n",a);
    a++;
}
int main(){
    int i;
    for(i=0;i<10;i++){
        mystatic();
    }
}
a=0
a=1
a=2
a=3
a=4
a=5
a=6
a=7
a=8
a=9

Process finished with exit code 0

代码块作用域以外的静态变量:
一旦全局变量定义static,意思是这个变量只是在定义这个变量的文件内部全局有效。(只能被定义这个变量的文件访问)


全局变量:
全局变量的存储方式和静态变量相同,但可以被多文件访问。


外部变量与extern关键字:
一个外部变量引入进来 extern int a;


全局变量和静态函数:
在c语言中函数默认都是全局的,使用关键词static可以将函数声明为静态。
只能在main.c这个函数中调用。

使得一个函数在不同文件下使用(调用):
在这里插入图片描述
在这里插入图片描述
在main()函数前声明,再调用,可以实现其他文件下的调用。或使用关键词extern
在这里插入图片描述
c语言中对于函数来说,上面两种写法都没问题的,但是对于变量就不一样的。
extern int age
int age 声明和定义两个含义,如果其他文件有则它定义,反之。


内存四区:
在这里插入图片描述


代码区:
代码区code,程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以在运行期间修改的。
int a;//不是代码,

#include <stdio.h>

int c=0;//静态区
int main(){
    static int d=0;//静态区
    int a=0;//栈区
    int b=0;//
    printf("%d,%d,%d,%d,%d\n",&a,&b,&c,&d,main);
    return 0;
}
6618652,6618648(栈区地址),4223024,4223028,(静态区地址)4199760(代码区地址)

Process finished with exit code 0

静态区:
所有的全局变量以及程序中的静态变量都存储在静态区。


栈区:
栈stack是一种先进后出的内存结构,所有的自动(auto)变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域,自动从栈中弹出。
栈区不会很大,一般是以k为单位的。
栈溢出:当栈空间满了,但还往栈内存压变量。

#include <stdio.h>

int c=0;//静态区
void test(int a,int b){
	//c语言中函数参数都是从右到左的顺序入栈的,先高后低
    printf("%d,%d\n",&a,&b);
}
int main(){
    static int d=0;//静态区
    int a=0;//栈区
    int b=0;//
    printf("%d,%d,%d,%d,%d\n",&a,&b,&c,&d,main);
    test(1,2);
    return 0;
}

在这里插入图片描述
形参在内存的栈区,所以与之前的两个内存地址相近。
在这里插入图片描述

函数的递归和栈关系:
递归的终止条件,防止栈溢出。

#include <stdio.h>

void recure(int n){
    if(n>0){
        printf("n1=%u\n",&n);
        recure(n-1);
        printf("n2=%u\n",&n);
    }
}

int main(){

    recure(5);
    return 0;
}

一个n,8个字节(64位)

n1=3755563772(Push)
n1=3755563740(Push)
n1=3755563708(Push)
n1=3755563676(Push)
n1=3755563644(Push)
n2=3755563644(Pop)
n2=3755563676(Pop)
n2=3755563708(Pop)
n2=3755563740(Pop)
n2=3755563772(Pop)

Process finished with exit code 0

对于自动变量,什么时候入栈,什么时候出栈,是不需要程序控制的,由c语言编译器实现。
不可操作的问题:

#include <stdio.h>

int *geta(){//函数的返回值是一个指针
    int a=100;
    return &a;
}//int a的作用域就在这里{}
int main(){
    int *p=geta();//这里得到一个临时栈变量的地址,这个地址在函数geta调用完之后已经无效了。
    *p=100;
    printf("%d\n",*p);
    return 0;
}

栈溢出:
在这里插入图片描述
对于一个32为操作系统,最大管理4G内存,其中1G是给操作系统自己的,剩下的3G都是给用户的,一个用户程序理论上使用3G的内存空间。


堆区:
堆heap和栈一样是一种程序运行中可以随时修改的内存区域,但没有栈那样先进后出的顺序。
堆是一个大容器,它的容量要远远大于栈,但是c语言中,堆内存空间的申请和释放需要手动通过代码来完成的。

malloc:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void print_array(int *p,int n){
    for(int i=0;i<n;i++){
        printf("p[%d]=%d\n",i,p[i]);
    }
}

int main(){
    int *p=(int *)malloc(sizeof(int) * 10);//在堆中间申请内存,在堆中申请了一个10和int类型大小的空间
    //(int *)强转类型
    memset(p,0,sizeof(int)*10);//内存清空
    for(int i=0;i<10;i++){
        p[i]=i;
    }
    print_array(p,10);

    char *p1=(char *)malloc(sizeof(char)*10);//在堆中申请了一个10个char这么大的空间
    free(p);//释放通过malloc分配的堆内存。
    free(p1);//
    //malloc和free是成对使用的

    return 0;
}

几个函数模型的合法性

//函数模型
int *geta(){//不能将一个栈变量的地址通过函数的返回值返回
    int a=0;//a是栈空间里的内容,栈变量
    return &a;//返回地址
}
int *geta1(){//可以通过函数的返回值返回一个堆地址,但记得用完之后free
    int *p=(int *)malloc(sizeof(int));//申请堆空间,地址是有效的
    return p;
}
int *geta2(){//合法,static处于静态去,程序运行一直有效。静态区不能free
    static int a=10;
    return &a;
}

int main(){
    int *getp=geta1();
    *getp=100;
    free(getp);
    
    int *getp1=geta2();
    *getp1=1000;
    //free(getp1);静态区不能free
    return 0;
}

在这里插入图片描述
那么为什么调用函数就会出错呢?
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
//内存泄露
void getheap(int *p){
    p=(int *)malloc(sizeof(int)*10);//这里得到了分配的空间
    p[0]=1;//可以将赋值操作和创建堆空间在一个函数内进行
    p[1]=2;
}//getheap执行完以后,p就不存在了。导致它指向的具体堆空间的地址编号也随之消失了。

void getheap1(int **p){
    *p=(int *)malloc(sizeof(int)*10);//这里得到了分配的空间
}

int main(){
    int *p=NULL;//定义一个指针,没有指向任何值。赋值为空
    //getheap(p);//实参没有任何的改变,下面进行的赋值操作是非法的
    getheap1(&p);
    //p=(int *)malloc(sizeof(int)*10);//这里若我们不调用函数处理这个p,那么程序不会出现问题。
    p[0]=1;
    p[1]=2;
    printf("p[0]=%d,p[1]=%d\n",p[0],p[1]);
    free(p);
    return 0;
}

单独正确的代码:

#include <stdio.h>
#include <stdlib.h>

void getheap1(int **p){
    *p=(int *)malloc(sizeof(int)*10);//这里得到了分配的空间
}
void *getheap2(){
	return malloc(100);
}

int main(){
    int *p=NULL;//定义一个指针,没有指向任何值。赋值为空
    getheap1(&p);//如果是通过一个函数的参数给实参分配堆空间内存,那么一定是二级指针的方式。

	p=getheap2();
	
    p[0]=1;//得到堆内存的地址,我们就可以进行操作
    p[1]=2;
    printf("p[0]=%d,p[1]=%d\n",p[0],p[1]);
    free(p);//释放
    return 0;
}

在这里插入图片描述


在堆中创建动态数组

int *array=malloc(sizeof(int)*i)//在堆中创建一个动态数组
//根据用户的输入来使用动态数组

堆中间创建超大数组

int set[10000000];//绝对会栈溢出的
int *set=malloc(sizeof(int)*10000000);//在堆中间创建超大数组
int i=0;
scanf("%d",&i);
int *p=calloc(i,sizeof(int));
for(int a;a<i;a++){
	scanf("%d",&p[a]);
}
for(int a;a<i;a++){
	printf("p[%d]=%d\n",a,p[a]);
}

堆、栈和内存映射:

每个线程都有自己的专属的栈(stack),先进后出(LIFO)
栈的最大尺寸固定的,超出则引起栈溢出
变量离开作用范围后,栈上的数据就会自动释放
堆上内存必须手动释放(C/C++),除非语言执行环境支持GC
有关栈和堆的选取:
1.明确知道数据的占用内存的多少
2.数据很小
3.大量内训
4.不明确需要多少内存

Code Area:程序代码指令,常量字符串,只可读

-Data Area
Static Area:存放全局变量/常量、静态变量/常量

–Dynamic Area
Heap:由程序员控制,使用malloc/free来操作
Stack:预先设置大小,自动分配与释放


#include <stdio.h>
#include <stdlib.h>

int x=1;//全局变量,静态区
int y=2;//全局变量,静态区

void TestFunction(int *arr,int x,int y,double *sum){
    int val=0;//栈里面
    if((x+y)<1024){//
        val=arr[x+y];
    }
    sum[val&3]+=val;
}

int main(){
    int *list_buf;//auto变量,栈区
    double total[4]={0.0,0.0,0.0,0.0};//数组,栈区
    list_buf=(int *)malloc(sizeof(int)*1024);//指向这个对地址指针
    TestFunction(list_buf,x,y,total);//函数的参数都是在栈里面的
    free(list_buf);
    return 0;
}

在这里插入图片描述


#include <stdio.h>
#include <stdlib.h>

const int a[4]={0,1,2,3};

static int UpdateCounter(char *b,int c,int a1){
    int d=0;
    const int e[4]={64,96,128,160};
    if(b[c++]>e[a1++]){
        d++;
    }
    return d;
};

int main(){
    char *b;
    int c=128;
    b=(char *)malloc(sizeof(char)*1024);
    UpdateCounter(b,c,a[1]);
    free(b);
    printf("c=%d,a1[1]=%d\n",c,a[1]);
    return 0;
}

在这里插入图片描述
栈的实现原理:
在这里插入图片描述

栈顶从高地址向低地址方向增长
存储非静态局部变量、函数参数、返回地址



#include <stdio.h>
#include <stdlib.h>

const int a[4]={0,1,2,3};

static int UpdateCounter(char *b,int c,int a1){
    int d=0;
    const int e[4]={64,96,128,160};
    if(b[c++]>e[a1++]){
        d++;
    }
    return d;
};

int main(){
    char *b;
    int c=128;
    b=(char *)malloc(sizeof(char)*1024);
    UpdateCounter(b,c,a[1]);
    free(b);
    printf("c=%d,a1[1]=%d\n",c,a[1]);
    return 0;
}

在这里插入图片描述


操作系统在管理内存的时候,最小单位不是字节,而是内存页(多字节成一个页4K),每页大小是一样的。


calloc

#include <stdio.h>
#include<stdlib.h>
#include <string.h>

int main(){

    //char *p=(char *)malloc(10);//定制房间,但没有清洁
    //memset(p,0,10);//自己设置(打扫)

    char *p=(char *)calloc(10,sizeof(char));//开完房间并打扫,分配了10*(sizeof(char))大小


    for(int i=0;i<10;i++){
        printf("%d\n",p[i]);
    }

    free(p);//释放

    return 0;
}

realloac

#include <stdio.h>
#include<stdlib.h>
#include <string.h>

int main(){

    char *p1=(char *)calloc(10,sizeof(char));//开完房间并打扫
    char *p2=(char *)realloc(p1,20);//在原有内存基础之上,在堆空间中增加连续的内存
    //char *p=realloc(NULL,20)=malloc(20)
    //如果原有内存没有连续的空间可以扩展那么会分配一个新空间将原有的内存copy到新空间,然后再释放空间
    //realloc和malloc一样,只分配内存不打扫
    for(int i=0;i<10;i++){
        printf("%d\n",&p2[i]);
    }

    free(p2);//释放,在释放的时候只要释放最后一个p2

    return 0;
}
-566221360
-566221359
-566221358
-566221357
-566221356
-566221355
-566221354
-566221353
-566221352
-566221351

Process finished with exit code 0

正确的模型,字符串常量
常量,静态变量,全局变量都在静态区一直有效

const char *getstring(){
	
	return "hello";//可以将有个常量的地址作为函数的返回值返回。
}

错误的模型,变量存在于栈空间中,函数消失占空间消失,地址无意义

char *getstring(){
	
	char array[10]="hello";
	return array;
}

正确的模型,和地址没关系,返回的是值

char getstring(){
	
	char c='c';
	return c;
}

正确的模型
修改一个静态变量,得到它的地址可以修改,间接的修改
static可以避免代码(全局变量)合并的时候,定义参数名字相同带来的编译错误

char *getstring(){
	
	static char array[10]="hello";//在静态区
	return array;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值