喜迎
春节

内存管理


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无名位域

一般用来填充或者调整成员之间的位置


文章作者: ljhaa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ljhaa !
评 论
 上一篇
宏定义
宏定义
1.不带参数的宏定义#define PI 3.14 将文中的所有的PI替换为3.14 #define PI 3.1415926 float radius = 2.0; float area = PI * radius * radius;
2023-05-02
下一篇 
函数
函数
1.函数1.函数的定义函数名是该函数的地址,所以在取址时可以不写&,直接写函数名,但是为了区分,最后写上 void print_c() ; // 声明函数 int main() { print_c() ;
2023-05-02
  目录