1.内存管理
1.1更灵活的内存管理方式
加载对应的库函数就可以申请动态的内存
对应的头文件#include<stdlib.h>
1.2 malloc
malloc
函数原型:void *malloc(size_t size) ;
malloc函数向系统申请分配size个字节的内存空间,并返回一个指向这块空间的指针。
返回的是void类型的函数指针,所以需要强制转换成需要的类型的指针。
#include<stdlib.h>
int main()
{
int *ptr ;
ptr = (int *)malloc(sizeof(int)) ; // 申请的是int类型的空间
if(ptr == NULL)
{
printf("分配内存失败! \n") ;
exit(1) ;
}
printf("请输入一个整数:") ;
scanf("%d",ptr) ;
printf("你输入的整数是:%d\n",*ptr) ;
}
1.3 free
注意:free释放完指针,指针还是在原来的位置,并不是野指针或者空指针,只是非法访问了。
int *ptr ;
ptr = (int *)malloc(sizeof(int)) ; // 申请的是int类型的空间
printf("你输入的整数是:%d\n",*ptr) ;
free(ptr) ;
printf("你输入的整数是:%d\n",*ptr) ; //free之后,在打印,就打印不出来了。
2.内存泄露
申请的动态内存没有及时释放,申请了内存空间,没有释放,就会占用电脑的内存
c语言不具备垃圾回收机制
下面的这个程序会卡死,不要轻易尝试,可以在虚拟机尝试。
3.malloc还可以申请一块任意尺寸的内存空间
# ...
int main()
{
int *ptr = NULL ;
int num ,i ;
printf("请输入待录入整数的个数:") ;
scanf("%d",&num) ;
ptr = (int *)malloc(num * sizeof(int)) ; // 申请指定的内存空间。
for(i = 0 ; i < num ; i++)
{
printf("%请录入第%d个整数:",i+1) ;
scanf("%d",&ptr[i]) ;
}
printf("你录入的整数是:") ;
for(i = 0 ; i < num ; i++)
{
printf("%d ", ptr[i] ) ;
}
putchar('\n') ;// 换行
free(ptr) ; // 释放内存空间。
}
4.初始化内存空间
下面的方法可以用java的数组Arrays的内置方法做比较来理解,不过c要考虑指针,麻烦了不少。
下面的这些函数与c语言中str开头的也有所不同,str开头的是针对字符串的,这个mem开头的是用来处理内存空间的。
4.1memset
函数声明: void *memset( void *dest, int c, size_t count );
案例:
int main()
{
int *ptr = NULL ;
int i ;
ptr = (int *)malloc(N * sizeof(int)) ;
// if(ptr == NULL) // 写上更加严谨,也可以不写这一段
// {
// exit(1) ;
// }
memset(ptr,0,N*sizeof(int)) ; // 指向,初始化的值,内存大小。
for(i = 0 ; i < N ;i++)
{
printf("%d " , ptr[i] );
}
putchar('\n') ;
free(ptr) ;
}
4.2 memcpy
函数声明:
void *memcpy( void *dest, const void *src, size_t count );
该函数将 src 的 count 个字节复制到 dest。该函数返回 dest 的起始位置。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[50] = { 0 };
char* b = "csdn.com";
memcpy(arr, b, strlen(b));
printf("%s", arr);
return 0;
}
4.3 memmove
函数声明:
void *memmove( void *dest, const void *src, size_t count );
该函数的作用和memcpy类似。但是为什么会有memmove呢?
我们看下面这段代码
#include <stdio.h>
#include <string.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr + 3, arr, 7);
int i = 0;
for (i = 0; i < 10; i++)
printf("%d ", arr[i]);
return 0;
}
我们可能认为答案是 1 2 3 1 2 3 4 5 6 7,但是结果并不是
与memcpy函数相比,memmove函数更加灵活,因为它能够处理源内存块和目标内存块之间可能会发生重叠的情况。在处理重叠的情况时,memmove函数会先将要复制的数据拷贝到一个临时缓冲区中,然后再将数据复制到目标内存块中,从而避免数据损坏或丢失。
5.calloc
申请内存空间,并默认初始化为0,相当于malloc和memset函数的结合应用
calloc申请内存空间时,只用写一部,而malloc需要两步。
6.realloc
是C标准库中的一个动态内存分配函数,用于重新分配已经分配的内存块的大小
void *realloc(void *ptr, size_t size);
int main()
{
int *ptr1 = NULL ;
int *ptr2 = NULL ;
// 第一次申请的内存空间
ptr1 = (int *)malloc(10 * sizeof(int)) ;
//进行若干操作发现ptr1申请的内存空间不够
//第二次申请的内存空间
ptr2 = (int *)malloc(20 * sizeof(int)) ;
//将ptr1的数据拷贝到ptr2中
memcpy(ptr2,ptr1,10) ;
free(ptr1) ;
//对ptr2申请的内存空间进行若干操作 。。。
free(ptr2) ;
}
int main()
{
int i , num ;
int cnt = 0 ;
int *ptr = NULL ; // 注意:这里必须初始化为NULL
do
{
printf("请输入一个整数(输入-1表示结束):") ;
scanf("%d",&num) ;
cnt ++ ;
ptr = (int *)realloc(ptr,cnt*sizeof(int)) ; //返回新的内存地址给ptr,可以存放最新的数据
if(ptr == NULL)
{
exit(1) ;
}
ptr[cnt-1] = num ;
}
while(num != -1) ;
printf("输入的整数分别是: ") ;
for(i = 0 ; i < cnt ; i++)
{
printf("%d ", ptr[i]) ;
}
putchar('\n') ;
free(ptr) ;
}
==注意==
该篇文章的内存管理,很多地方都可以理解为java数组的内存管理,以及初始化数组啊之类的。
7.c语言的内存布局
7.1内存布局规律
打印各种变量的地址和函数的地址,看有什么规律
int global_uninit_var ;
int global_uninit_var1 = 520 ; //两个全局变量
int global_uninit_var2 = 880 ;
void func(void) ; // 声明函数
void func(void)
{
}
int main(void)
{
int local_var1 ; //两个局部变量
int local_var2 ;
static int static_uninit_var ; //两个静态变量,一个初始化,一个未初始化
static int static_init_var = 456;
char *str1 = "I love FishC.com!" ; // 两个字符串数组
char *str2 = "You are right!" ;
int *malloc_var = (int *)malloc(sizeof(int)) ; // 申请内存空间地址
printf("addr of func -> %p\n",func) ;
printf("addr of str1 -> %p\n",str1) ;
printf("addr of str2 -> %p\n",str2) ;
printf("addr of global_uninit_var1 -> %p\n",global_uninit_var1) ;
printf("addr of global_uninit_var2 -> %p\n",global_uninit_var2) ;
printf("addr of static_uninit_var -> %p\n",static_uninit_var) ;
printf("addr of static_init_var2 -> %p\n",static_init_var) ;
printf("addr of global_uninit_var -> %p\n",global_uninit_var) ;
printf("addr of malloc_var -> %p\n",malloc_var) ;
printf("addr of local_var1 -> %p\n",&local_var1) ;
printf("addr of local_var2 -> %p\n",&local_var2) ;
}
根据上图的运行结果可以发现。下面的规律。
7.1.1代码段
str1 和 str2是在代码段中的
7.1.2数据段
7.1.3 BSS段
8.堆
9.栈
10.栈和堆的区别
10.1生存周期
// 生存周期的案例
int *func(void)
{
int *ptr = (int *)malloc(sizeof(int)) ;
if(ptr == NULL)
{
exit(1) ;
}
*ptr = 520 ;
return ptr ;
}
int main(void)
{
int *ptr = NULL ;
ptr = func() ;
printf("%d\n",*ptr) ; //最后输出的结果为520.
free(ptr) ; // 在main函数中释放内存空间,因为只有在main函数中才被使用了,因此在main函数汇总调用free函数
// 如果没有返回值的函数,应该在函数内部释放空间
}
10.2发展方向
int main(void)
{
int *ptr1 = NULL ;
int *ptr2 = NULL ;
ptr1 = (int *)malloc(sizeof(int)) ;
ptr2 = (int *)malloc(sizeof(int)) ;
printf("stack: %p -> %p\n",&ptr1,&ptr2) ;
printf("heap: %p -> %p\n",ptr1,ptr2) ;
}
结果可以看出
栈是从高地址向地址发展的
堆是从低地址向高地址发展的。
11.内存池
如果一直使用malloc和free函数,申请释放内存空间的话,可能会产生内存碎片
就是申请了一片空间,再释放之后,重复多次,每个空间都没有连在一起,就成为了碎片,这样描述是便于理解,不是完全正确
为了解决这个问题,有了内存池
内存池:让程序额外维护一个缓存区域
12.位域
#include<stdio.h>
int main()
{
struct Test
{
unsigned int a:1 ;
unsigned int b:1 ;
unsigned int c:2 ;
};
struct Test test ;
test.a = 0 ;
test.b = 1 ;
test.c = 2 ; // 因为二进制中的2是10,所以需要两个位置来存放,所以上面的位域写的是2
printf("a = %d, b = %d, c = %d\n",test.a, test.b , test.c) ;
printf("size of test = %d\n",sizeof(test)) ;
}
==位域的字节必须是小于数据类型的位的,例如,int是32位,所以不能超过32。
12.2无名位域
一般用来填充或者调整成员之间的位置