C温故补缺(十四):内存管理

内存管理

stdlib库中有几个内存管理相关的函数

序号函数和描述
1void *calloc(int num, int size);
在内存中动态地分配 num 个长度为size 个字节 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
2void free(void *address);
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3void *malloc(int size);
在堆区分配一块size个字节的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4void *realloc(void *address, int newsize);
该函数重新分配内存,把内存扩展到 newsize,newsize的单位还是字节

总之三个alloc分配的都是字节,calloc分配num个size字节的空间,malloc分配size个字节的空间,realloc重新分配size个字节的空间,三个都返回分配的空间的基址,然后用一个指针存这个基址,free()用来释放掉这个指针.

如:

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

int main(){
    int *a=calloc(10,4);
    realloc(a,80);
    for(int i=0;i<20;i++){
        *(a+i)=2*i;
    }
    for(int i=0;i<20;i++){
        printf("%d ",*(a+i));
    }
    free(a);
}

分配10个4字节空间,一个4字节的空间可以存一个int或long,之后重新分配80个字节,也就是之前空间的两倍

两次for循环存数,读数,最后释放掉指针

问题1:

重新分配内存会不会把之前的存储释放?

实验:

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

int main(){
    int *a=malloc(4);
    *a=78;
    printf("%d\n",*a);
    realloc(a,8);
    *(a+1)=99;
    printf("%d %d",*a,*(a+1));
}

结果:

所以重新分配内存不会释放掉之前的内存,而是在原内存的基础上添加新的空间,而且如果重新分配的内存比之前的空间小,并不会截断之间后面的内存,还是可以访问的

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

int main(){
    int *a=malloc(8);
    *a=78;
    *(a+1)=99;
    realloc(a,4);
    printf("%d %d",*a,*(a+1));
}

问题2:

如果只分配4字节的空间,但是强制使用大于4字节的空间,能正常存取吗?

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

int main(){
    int *a=malloc(4);
    for(int i=0;i<4;i++){
        *(a+i)=i+4;
    }
    for(int i=0;i<4;i++){
        printf("%d ",*a);
        a++;
    }
}

输出

存取成功!也就是说,实际分配的空间不管多大,当指针移动时,都是按数据的类型移动的,并按数据类型对齐,所以能正确的存取数据

看调试过程,a的地址是每次增加4的

如果不用alloc分配内存,直接用一个指针呢?

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

int main(){
    int *a;
    a++;
    a++;
    *a=1;
    printf("%d ",*a);
}

断点调试时可以看到a的地址是0x10,每次a++,地址+4

每次a++的反汇编代码是add指令

在对*a赋值时出现Segmentation fault异常,也就是不能对*a赋值

segmentation fault:随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.

查看反汇编的内存地址

可以看到0x3fffff以前的内存是无法访问的,从0x400000才是程序能用的地址

猜测:因为内存的分段:

0x3fffff之前的内存空间都是属于kernal的,所以不能用

那如果给指针初始化一个可使用的内存空间呢?

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

int main(){
    int z;
    int *a=&z;
    *a=1;
    printf("%d ",*a);
}

成功,这也是正常的调用

断点调试看内存地址:

问题3:

如果给指针初始化一个int型的地址,它会自动对齐后续的空间吗?

若继续后移指针,并存数取数:

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

int main(){
    int z;
    int *a=&z;
    *a=1;
    printf("%d ",*a);
    a++;
    *a=2;
    printf("%d ",*a);
}

结果只输出了一个,但也没有报错,再断点调试看看:

可以看到在后移指针后,在给*a赋值,a的地址发生了改变,也就说*a=a,a指向本身?

验证:

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

int main(){
    int z;
    int *a=&z;
    *a=1;
    printf("%d ",*a);
    a++;
    printf("%d %d",a,*a);
}

尝试*a=&x

正常情况下,如

int *a;
int x;
*a=&x;

这样是错误的,会报异常:assignment to 'int' from 'int *' makes integer from pointer without a cast,即从“int *”赋值到“int”会使指针中的整数没有强制转换,因为a是一个int型指针,*a得到的应该是一个int型,&x则是一个int *,这个操作相当于把一个int *赋值给一个int

但是由上个问题实验得知,初始化后的指针后移会指向本身,所以int是可以存int*的

实践:

#include<stdio.h>
int main(){
    int z=1;
    int *a=&z;
    a++;
    int x;
    *a=&x;
    *a=4;
    printf("%d",*a);

}

虽然有warning,但是成功输出了,证明了此时a就是指向本身,同时也证明了c语言中的数据类型是互用存储的,只要字节长度相同就能存,int ,int *只是一个规定的标签罢了

运行完*a=&x后,a的值变成了x的地址

问题4:

如果只是在int *前有int型变量初始化,那么指针会自动对齐

如:

#include<stdio.h>
int main(){
    int a=1;
    int *p;
    *p=3;
    p++;
    *p=5;
}

调试过程:

而且可以看到,指针后移之后,*p被初始化成了0

问题总结

alloc分配内存时会自动对齐,即使分配的空间很小,也能继续使用空间

int a;如果不赋值的化,就会被编译器优化掉

直接对指针操作,无法赋值,因为初始它指向的是不能访问的内核空间

如果给指针赋值&a,并不会自动对齐后续的空间,p++得到的是&a++,实际上,p++后指针指向本身

但是如果给变量赋了初值,int a=1;之后对指针直接操作,是自动对齐的,且,p++后的*p会初始化成0

以上探究只是为了搞清楚内存分配的实质,但在项目中不要随便用,这些可能属于未定义行为,很容易导致异常

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值