2021-09-02

本文探讨了C++中的内存管理函数calloc、realloc的用法,以及堆栈区的不同特性。涉及动态内存分配、结构体内存分配、内存碎片问题和链接结构。重点讲解了如何正确使用这些函数,以及堆栈在程序中的角色和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.calloc

int main()
{
    int n=5;
    //int *ip=(int*)malloc(sizeof(int)*n);
    int *ip=(int*)calloc(sizeof(int)*n);
    for(int i=0;i<n;i++)
    {
        printf("&x\n",ip[i]);//ip[i]=>*(ip+i)//加一表示加了一个整形字节数
    }
    free(ip);//释放的是ip所指向的空间还给系统,并不是把ip释放掉
    ip=nullptr;
    return 0;
}

my_calloc()

void* my_calloc(size_t num,size_t size)
{
    void* s=malloc(num*size);
    if(s!=nullptr)
    {
        memset(s,0,num*size);//只有调用该函数,才能变成0
    }
    return s;
}

free

//vs 2019
int main()
{
    int* ip=(int*)malloc(sizeof(int));
    printf("%x\n",*ip);//cd cd cd cd//随机填充
    *ip=10;
    printf("%x\n",*ip);//00 00 00 0a
    free(ip);
    printf("%x\n",*ip);//ef ef ef ef//随机填充
    *ip=100;
    printf("%x\n",*ip);//00 00 00 64
    free(ip);
    return 0;
}

不能对同一个空间释放两次

int main()
{
    int n=10;
    int* ip=(int*)malloc(sizeof(int)*n);
    if(ip==nullptr) exit(EXIT_FAILURE);
    for(inti=0;i<n;++i)
    {
        ip[i]=i;
    }
    
    ip=(int*)realloc(ip,sizeof(int)*5);
    free(ip);
    return 0;
}

2.realloc

realloc扩充之前分配的内存块(重新分配内存块)

void *realloc(void *ptr,size_t new_size)

重新分配给定的内存区域。它必须是之前位malloc()、calloc()或realloc()所分配,并且仍未被free或realloc的调用所释放。否则,结果未定义。

重新分配按以下二者之一执行:

a>可能的话,扩张或收缩ptr所指向的已存在内存。内容在新旧大小中的较小者范围内保持不变,若扩张范围,则数组新增部分的内容是未定义的。

b>分配一个大小为new_size字节的新内存块,并复制大小等于新旧大小中较小者的内存区域,然后释放旧内存块。

若无足够内存,则不释放旧内存块,并返回空指针。

若ptr是NULL,则行为与调用malloc(new_size)相同。

若new_size为零,则行为是实现定义的(可返回空指针,此情况下可能或可能不释放旧内存,或返回不会用于访问存储的非空指针)。

realloc是线程安全的:它表现得如同只访问通过其参数可见的内存区域,而非任何静态存储。

先前令free或realloc归还一块内存区域的调用,同步于任何分配函数的调用,包括分配相同或部分相同内存区域的realloc。这种同步出现于任何分配函数所做的内存访问后,任何realloc所做内存访问前,所有操作一块特定内存区域的分配及解分配函数拥有单独全序。

参数:

​ ptr:指向需要重新分配的内存区域的指针

​ new_size:数组新的大小(字节数)

返回值

​ 成功时,返回指向新分配内存的指针。返回的指针必须用free()或realloc()归还。原指针ptr被非法化,而且任何通过它的访问时未定义行为(即便重分配是就地的)。

​ 失败时,返回空指针。原指针ptr保持有效,并需要通过free()或realloc()归还。

第一种情况:后续未分配内存空间足够大,可以分配空间。

int main()
{
    int n=5,m=10;
    int *ip=(int*)malloc(sizeof(int)*n);//20
    if(NULL=ip) return 1;
    for(int i=0i<n;i++)
    {
        ip[i]=i;
    }
    //...
    
    ip=(int*)realloc(ip,sizeof(int)*m);//40
    if(NULL==ip) return 1;
    
    //...
    free(ip);
    ip=NULL:
}

第二种情况:后续未分配内存空间不足够大,不能分配空间

void *my_realloc(void* p,size_t old_sz,size_t nes_sz)
{
    void *vp=malloc(new_sz);
    if(vp==NULL) return NULL;
    if(p!=NULL)
    {
        memmove(vp,p,old_sz);
    }
    return vp;
}

第三种情况:堆内存不足,扩展空间失败,函数返回NULL

int main()
{
    int n=5,m=10;
    int* ip=(int*)malloc(sizeof(int)*n);
    if(ip==NULL) return 1;
    for(int i=0;i<n;i++)
    {
        ip[i]=i;
    }
    
    ip=(int*)realloc(ip,sizeof(int)*m);
    
    int *newdata=(int*)realloc(ip,sizeof(int)*m);
    if(newdata==NULL)
    {
        free(ip);
        return 1;
    }
    ip=newdata;
    
    return 0;
}

3.动态管理函数与字符串

void GetStr(char *&p,int n)
{
	*p=(char*)malloc(sizeof(char)*n);
    if(*p==NULL) exit(EXIT_FAILURE);
}
char* GetString(int n)
{
	return (char*)malloc(sizeof(char)*n);
}
void GetString(char **p,int n)
{
	*p=(char*)malloc(sizeof(char)*n);
    if(*p==NULL) exit(EXIT_FAILURE);
}
int main()
{
	int n=100;
	char *cp=NULL;
	//GetString(&cp,n);
	strcpy(cp,"hello tulun");
	printf("%s",cp);
	pritnf(cp);
	free(cp);
	return 0;
}

&:【c++中】

取地址;位于;引用【标识符都是从右向左解释】

int main()
{
    int a=10;
    int b=a;
    int &c=a;
}

4.堆区与栈区的区别

1)管理方式:栈由系统自动管理;堆由程序院控制,使用方便,但是易产生内存泄漏。

2)生长方式:栈由低地址扩展(即“向下生长”),是连续的内存区域;堆向高地址扩展(即“向上生长”),时不连续的内存区域。这是由于堆区管理系统用链表来存储空闲内存地址,自然不连续,二链表从低地址向高地址遍历。

3)空间大小:栈顶地址和栈的最大容量由系统预先规定(通常默认1M或10M);堆的大小则受限于计算机系统中有效的虚拟内存,32为Linux系统中堆内存可达2.9G空间。

4)存储内容:栈在函数调用时,首先压入是函数实参,然后主调函数中下条指令(函数调用语句的下条可执行语句)的地址压入,最后是被调函数的局部变量。本次调用结束后,局部变量先出栈,指令地址出栈,最后栈平衡,程序由该点继续下条可执行语句。堆通常在头部用一个字节存放其大小,堆用于存储生存期于函数调用无关的数据,具体内容由程序员安排。

5)分配方式:栈课静态分配或动态分配。静态分配由编译器完成,如局部变量的分配。动态分配由alloca函数在栈上申请空间,用完后自动释放不需要调动free函数。堆只能动态分配且手工释放。

6)分配效率:栈由计算机底层提供支持:分配专门的寄存器存放栈地址,压栈出栈由专门的指令执行,因此效率较高。堆由函数库提供,机制复杂,效率比栈低得多。

7)分配后系统响应:只要栈残余空间大于申请空间,系统将为程序提供内存,否则报告异常提示栈溢出。

操作系统为堆维护一个记录空闲内存地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点空间分配给程序。若无足够大小的空间(可能由于内存碎片太多),有可能调用系统功能去增加程序数据段的内存空间,一边有机会分到足够大小的内存,然后进行返回,大多数系统会在该内存空间首地址处记录本次分配的内存大小,供后续的释放函数(如free/delete)正确释放本内存空间。

8)碎片问题:栈不会存在碎片问题,因为栈是先进后出的队列,内存块弹出栈之前,在其上面的后进的栈内容已弹出。二频繁申请释放操作会造成堆内存空间的不连续,从而造成大量碎片,使程序效率降低。

可见,堆容易造成内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和内核态切换,内存申请的代价更为昂贵。所以栈在程序中应用最广泛,函数调用也利用栈来完成,调用过程中的参数、返回地址。栈基指针和局部变量等都采用栈的方式存放。所以,建议尽量使用栈,仅在分配大量或大块内存空间时使用堆。

最后使用栈和堆应避免越界发生,否则可能程序崩溃或破坏程序堆、栈结构,产生意想不到的后果。

5.动态内存管理与结构体

结构体变量和内置类型 都有局部,全局,动态生存期。

struct Student
{
    char s_name[10];
    int s_age;
};
struct Student s1;
int mian()
{
    struct Student s2;
    struct Student* sp=(struct Student*)malloc(sizeof(struct Student));
    sp->age=100;
    free(sp);
    sp=nullptr;
    return 0;
}


int max=10;
int main()
{
    int a=10;
    int* ip=(int*)malloc(sizeof(int));
    *ip=100;
    free(ip);
    ip=nullptr;
    return 0;
}
struct Student
{
    char s_name[10];
    int s_age;
    float score;
};
struct Student g_studa;//全局
struct Student g_studb={"akashi",18.150};
int main()
{
    struct Student studa;//局部未初始化
    struct Student studb={"akashi",14,140.5};
    struct Student* sp1=(struct Student*)malloc(sizeod(struct Student));//
    struct Student* sp2=(struct Student*)malloc(sizeod(*sp2));//
  	struct Student* sp3=(struct Student*)malloc(sizeod(sp3));//error
    strcpy(sp1->s_name,"akashi");
    sp1->age=11;
    sp1->score=144.5;
    free(sp1);
    free(sp2);
    free(sp3);
}

6.结构体也可以嵌套指向自身的指针

struct Student
{
    char s_name[10];//姓名
    int s_age;      //年龄
    float score;    //成绩
    struct Student* next;//指向Student类型的指针变量属性
};

我们可以一个个的动态申请struct Student类型的内存空间,然后用next指针把他们链接起来。

7.柔性数组

struct sd_node
{
    int num;
    int size;
    char data[];
};
//或
struct sd_node
{
    int num;
    int size;
    char data[0];
};

数组的大小声明为0,或者不给出大小,称之为柔性数组。

注意:全局数组和局部数组不能这样定义。

柔性数组是一种数组大小待定的数组。

在C语言中,可以使用结构体产生的柔性数组,结构体的最后一个元素可以是大小未知的数组。

struct StrNode
{
    int len;
    int size;
    char data[];
};
int main()
{
    struct StrNode* sp=nullptr;
    int n=0;
    scanf_s("%d",&n);
    sp=(struct StrNode*)malloc(sizeof(struct StrNode)+sizeof(char)*n);//28
    strcpy_s(sp->data,n,"china");
    sp->len=5;
    sp->size=20;
    return 0;
}

8.二位数组

int main()
{
	int **ip=nullptr;
	int row,col;
	scanf("%d %d",&row,&col);//3 5
	ip = (int **)malloc(sizeof(int *) *row);
	int ar[row][col]={};
	for(int i = 0; i < row; i ++)
	{
        ip[i] = ar[i];//将二维数组行地址赋值到对应的一维指针上。
    }
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值