带你学C带你飞 | 动态内存管理 | C语言的内存布局 | 高级宏定义 | 内联函数 | 结构体 | 结构体数组 | 结构体指针

本文深入探讨C语言的动态内存管理,包括malloc、free、calloc、realloc库函数的使用及注意事项,强调了内存泄漏的危害。此外,文章详细介绍了C语言的内存布局,包括代码段、数据段、BSS段、栈和堆。高级宏定义部分讲解了宏定义的类型和使用技巧,内联函数的概念和优化作用。结构体的使用、结构体数组、结构体指针以及如何传递结构体变量和指针也被详细阐述。内容丰富,适合C语言初学者和进阶学习者。
摘要由CSDN通过智能技术生成

一、动态内存管理

1.动态内存管理

  C语言有灵活的内存管理方式,只需要使用几个库函数就可以搞定。这些库函数都包含在stdlib.h的头文件中。

  • malloc:申请动态内存空间
  • free:释放动态内存空间
  • calloc:申请并初始化一系列内存空间
  • realloc:重新分配内存空间
1.1 malloc库函数
  • 函数原型:

    void *malloc(size_t size);
    

  malloc函数向系统申请分配size个字节的内存空间,并返回一个指向这块空间的指针(void类型的指针)。申请的这一块空间并没有初始化为0,也就是这块空间是随机的。
  如果函数调用成功,返回一个指向申请的内存空间的指针,由于返回类型是void指针(void *),所以它可以被转换成任何类型的数据;如果函数调用失败,返回值是NULL。另外,如果size参数设置为0,返回值也可能是NULL,但这并不意味着函数调用失败。
  由于malloc函数申请的空间位于内存的堆上,如果不主动的释放堆上资源,将永远存在,直到程序被关闭。因此,当不再使用这块内存的时候,请务必动手释放内存,否则可能造成内存泄露!
举个栗子:

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

int main()
{
   
        int *ptr;
        
        ptr = (int *)malloc(sizeof(int));
        
        if (ptr == NULL)
        {
          
                printf("分配内存失败!\n");
                exit(1);
        }       
        
        printf("请输入一个整数:");
        scanf("%d",ptr);
        
        printf("您输入的整数是:%d\n",*ptr);
        return 0;
}
[liujie@localhost sle37]$ gcc test.c && ./a.out
请输入一个整数:5
您输入的整数是:5

  malloc函数还可以申请一块任意尺寸的内存空间。由于申请得到的空间是连续的,因此,我们可以使用数组来进行索引。
举个栗子:

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

int main(void)
{
   
        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);

        return 0;
}
[liujie@localhost sle37]$ gcc test2.c && ./a.out
请输入待输入整数的个数:5
请录入第1个整数:23
请录入第2个整数:43
请录入第3个整数:12
请录入第4个整数:46
请录入第5个整数:36
你录入的整数是:23 43 12 46 36
1.2 free库函数
  • 函数原型

    void free(void *ptr);
    

  free函数释放ptr参数指向的内存空间。该内存空间必须是由malloccallocrealloc函数申请的。否则,该函数将导致未定义行为。如果ptr参数是NULL,则不执行任何操作。注意:该函数并不会修改ptr参数的值,所以调用后它仍然指向原来的地方(变为非法空间)。
举个栗子:

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

int main()
{
   
        int *ptr;
        
        ptr = (int *)malloc(sizeof(int));
        
        if (ptr == NULL)
        {
          
                printf("分配内存失败!\n");
                exit(1);
        }       
        
        printf("请输入一个整数:");
        scanf("%d",ptr);    
        printf("您输入的整数是:%d\n",*ptr);

		free(ptr);
        printf("您输入的整数是:%d\n",*ptr);
        return 0;
}
[liujie@localhost sle37]$ gcc test.c && ./a.out
请输入一个整数:5
您输入的整数是:5
您输入的整数是:0

  不合理的大量申请内存空间的行为叫做内存泄露。为了防止内存泄露,我们应该在使用完内存空间后立刻调用free函数来释放掉。因此,malloc函数与free函数是成对编写的。有些编程语言(比如:java、python)具备垃圾回收机制,不需要手动释放内存空间。但是,C语言不具备垃圾回收机制。
  还有一种可能造成内存泄露的情况就是丢失内存块的地址。
举个栗子:

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

int main()
{
   
        int *ptr;
        int num = 123;

        ptr = (int *)malloc(sizeof(int));

        if (ptr == NULL)
        {
   
                printf("分配内存失败!\n");
                exit(1);
        }

        printf("请输入一个整数:");
        scanf("%d",ptr);

        printf("您输入的整数是:%d\n",*ptr);

        ptr = &num;
        printf("您输入的整数是:%d\n",*ptr);
        free(ptr);

        return 0;
}
[liujie@localhost sle37]$ gcc test1.c && ./a.out
请输入一个整数:520
您输入的整数是:520
您输入的整数是:123
*** Error in `./a.out': free(): invalid pointer: 0x00007fff0f5b7924 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81329)[0x7f7fd8159329]
./a.out[0x400745]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7f7fd80fa555]
./a.out[0x4005f9]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:00 13051169                           /home/liujie/FishC/sle37/a.out
00600000-00601000 r--p 00000000 fd:00 13051169                           /home/liujie/FishC/sle37/a.out
00601000-00602000 rw-p 00001000 fd:00 13051169                           /home/liujie/FishC/sle37/a.out
01d82000-01da3000 rw-p 00000000 00:00 0                                  [heap]
7f7fd3dea000-7f7fd3dff000 r-xp 00000000 fd:00 84                         /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f7fd3dff000-7f7fd3ffe000 ---p 00015000 fd:00 84                         /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f7fd3ffe000-7f7fd3fff000 r--p 00014000 fd:00 84                         /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f7fd3fff000-7f7fd4000000 rw-p 00015000 fd:00 84                         /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f7fd4000000-7f7fd4021000 rw-p 00000000 00:00 0 
7f7fd4021000-7f7fd8000000 ---p 00000000 00:00 0 
7f7fd80d8000-7f7fd829c000 r-xp 00000000 fd:00 15628                      /usr/lib64/libc-2.17.so
7f7fd829c000-7f7fd849b000 ---p 001c4000 fd:00 15628                      /usr/lib64/libc-2.17.so
7f7fd849b000-7f7fd849f000 r--p 001c3000 fd:00 15628                      /usr/lib64/libc-2.17.so
7f7fd849f000-7f7fd84a1000 rw-p 001c7000 fd:00 15628                      /usr/lib64/libc-2.17.so
7f7fd84a1000-7f7fd84a6000 rw-p 00000000 00:00 0 
7f7fd84a6000-7f7fd84c8000 r-xp 00000000 fd:00 15621                      /usr/lib64/ld-2.17.so
7f7fd86ae000-7f7fd86b1000 rw-p 00000000 00:00 0 
7f7fd86c3000-7f7fd86c7000 rw-p 00000000 00:00 0 
7f7fd86c7000-7f7fd86c8000 r--p 00021000 fd:00 15621                      /usr/lib64/ld-2.17.so
7f7fd86c8000-7f7fd86c9000 rw-p 00022000 fd:00 15621                      /usr/lib64/ld-2.17.so
7f7fd86c9000-7f7fd86ca000 rw-p 00000000 00:00 0 
7fff0f598000-7fff0f5b9000 rw-p 00000000 00:00 0                          [stack]
7fff0f5cf000-7fff0f5d1000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
已放弃(吐核)

  总结一下,导致内存泄漏主要有两种情况:

  • 隐式内存泄漏(即用完内存块没有及时使用free函数释放)
  • 丢失内存块地址
1.3 初始化内存空间

  以mem开头的函数被编入字符串标准库,函数的声明包含在string.h这个头文件中:

  • memset --使用一个常量字节填充内存空间
  • memcpy --拷贝内存空间
  • memmove --拷贝内存空间
  • memcmp --比较内存空间
  • memchr --在内存空间中搜索一个字符

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 10

int main(void)
{
   
        int *ptr = NULL;
        int i;

        ptr = (int *)malloc(N * sizeof(int));
        if (ptr == NULL)
        {
   
                exit(1);
        }

        //使用常量0填充内存空间
        memset(ptr,0,N * sizeof(int));

        for (i = 0;i < N;i++)
        {
   
                printf("%d ",ptr[i]);
        }
        putchar('\n');

        free(ptr);

        return 0;
}
[liujie@localhost sle37]$ gcc test3.c && ./a.out
0 0 0 0 0 0 0 0 0 0 
1.4 calloc库函数
  • 函数原型

    void *calloc(size_t nmemb,size_t size);
    

  calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb*size),这些内存空间全部被初始化为0。
  calloc函数与malloc函数的一个重要区别是:

  • calloc函数在申请完内存后,自动初始化该内存空间为零
  • malloc函数不进行初始化操作,里边数据是随机的

  下面两种写法是等价的:

//calloc()分配空间并初始化
int *ptr = (int *)calloc(8,sizeof(int));
//malloc()分配内存空间并用memset()初始化
int *ptr = (int *)malloc(8 * sizeof(int));
memset(ptr,0,8 * sizeof(int));
1.5 realloc库函数

  有一种情况:对原来分配的内存空间进行扩展。需要重新使用malloc函数申请更大的空间,再使用memcpy函数将原先申请的空间拷贝到更大的空间。这样操作比较繁琐,可以直接使用realloc函数(重新分配内存空间)一步到位!

  • 函数原型

    void *realloc(void *ptr,size_t size);
    

以下几点是需要注意的:

  • realloc函数修改ptr指向的内存空间大小为size字节
  • 如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果新的内存空间大小小于旧的内存空间,可能会导致数据丢失,慎用!
  • 该函数将移动内存空间的数据并返回新的指针
  • 如果ptr参数为NULL,那么调用该函数就相当于调用malloc(size)
  • 如果size参数为0,并且ptr参数不为NULL,那么调用该函数就相当于调用free(ptr)
  • 除非ptr参数为NULL,否则ptr的值必须由先前调用malloccallocrealloc函数返回

2.课后作业

  1. malloc 函数的原型是这样的 void *malloc(size_t size);,那你知道 size_t 实际上是什么类型吗?
    答:size_t 实际上就是 unsigned int(无符号整型),在 64 位系统中是被定义为 long unsigned int。

  2. C 语言动态内存管理中最令人切齿痛恨的是什么吗?
    答:使用 malloc 申请了内存空间,但用完之后没有用 free 及时释放空间。

  3. 下面代码中,如果第一行打印的是“Before free, ptr = 0x8b23008”,请问第二行打印的是什么?

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
         
            int *ptr = NULL;
    
            ptr = (int *)malloc(sizeof(int));
            if (ptr == NULL)
            {
         
                    printf("内存分配失败!\n");
                    exit(1);
            }
    
            printf("Before free, ptr = %p\n", ptr);
            free(ptr);
            printf("After free, ptr = %p\n", ptr);
    
            return 0;
    }
    

    答:第二行代码打印的是“After free, ptr = 0x8b23008”。
    这里务必要注意一点,free() 函数释放的是 ptr 指向的内存空间,但它并不会修改 ptr 指针的值。也就是说,ptr 现在虽然指向 0x8b23008,但该空间已经被 free() 函数释放了,所以对于该空间的引用已经失去了意义(会报错)。因此,为了防止后面再次对 ptr 指向的空间进行访问,建议在调用 free() 函数后随即将 ptr 赋值为 NULL。

  4. 请问下面划红线位置应该填什么?
    在这里插入图片描述
    答:str = (char *)malloc(1024);

  5. 请问调用一次 malloc 函数可以申请多块内存空间吗?
    答:不行,调用一次 malloc 函数只能申请一块连续的内存空间,只是其尺寸可以自定义。

  6. 以 mem 开头的函数比如 memcpy,memcmp 被编入字符串标准库(函数的声明包含在 string.h),那么请问它们与同在该标注库的 strncpy,strcnmp 函数有什么区别呢?
    答:从形式上看,str 开头的函数使用的是 char 类型的指针(char *)作为参数和返回值;而 mem 开头的函数使用的是 void 类型的指针(void *)作为参数和返回值。
    本质上看,str 开头的函数主要目的是提供字符串的拷贝,比较等操作;而 mem 开头的函数主要目的是提供一个高效的函数接口来处理内存空间的数据。

  7. 请用 malloc 函数实现 calloc(1024, sizeof(int)) 函数得到的结果?
    答:calloc 函数在申请完内存后,自动初始化该内存空间为零,而 malloc 函数不进行初始化操作,所以调用完 malloc 函数之后,需要再调用 memset 函数将内存初始化为零。

    int *ptr = (int *)malloc(1024 * sizeof(int));
    memset(ptr, 0, 1024 * sizeof(int));
    
  8. realloc(NULL, 1024) 的含义是?
    答:申请 1024 个字节的内存空间,并返回地址,相当于 malloc(1024)。

  9. 请问下面代码存在什么问题?

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
         
            static int *pi = (int *)malloc(sizeof(int));
    
            *pi = 520;
            printf("*pi = %d\n", *pi);
    
            return 0;
    }
    

    答:上面程序编译会报错,这是因为初始化静态变量时不能调用函数。static 声明的变量在程序运行过程中是始终存在的,通常在 main 函数运行之前就完成了初始化过程。
    但 malloc 函数的调用是在 main 函数之后进行的,所以从概念上来说,static 声明的变量不可能通过调用库函数来进行初始化。同样的道理,这个规则对于全局变量来讲也是一样的!
    对于静态变量来说,可以通过在后面用一个单独的语句给变量分配内存来避免这个问题:

    ……
            static int *pi;
            pi = (int *)malloc(sizeof(int));
    ……
    
  10. 编写一个程序,让用户决定要录入的整数个数,然后再申请相应的内存空间来存放(要求使用 malloc 函数来申请内存空间)。

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
         
            int *ptr = NULL;
            int num, i;
    
            printf("请输入待录入整数的个数:");
            scanf("%d", &num);
    
            ptr = (int *)malloc(num * sizeof(int))
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值