嵌入式开发学习(第二阶段 C语言笔记)

发布于:2025-05-30 ⋅ 阅读:(14) ⋅ 点赞:(0)

  • 动态内存分配

动态内存分配

要实现动态内存分配,需要使用标准C库提供的库函数,我们所说的动态内存分配,其实就是在堆区申请内存(此时的内存回收需要程序员自身来维护)

常用函数

malloc
  • 头文件:#include <stdlib.h>

  • 函数原型:

    void* malloc(size_t szie);
    
  • 功能:分配指定字节数的内存到堆区,返回指定范围内存块首地址的指针。内存内容未初始化(随机值)

  • 参数:

    • size:要分配的内存大小(字节),这里是size_t是数据类型unsigned long int别名
  • 返回值

    • 成功:返回内存指针(申请到的内存的首地址)
    • 失败:失败返回 NULL
  • 示例

    /*************************************************************************
      > File Name:    demo03.c
      > Author:       小刘
      > Description:  
      > Created Time: 2025年05月23日 星期五 14时46分56秒
     ************************************************************************/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc,char *argv[])
    {
        // 创建一个指针变量,用来接收内存分配后返回的内存地址
        int *p = malloc(sizeof(int));// 4字符
    
        // 对于指针的使用,一定要进行校验,防止野指针
        if(p == NULL)
        {
            printf("内存申请失败!\n");
            return -1;
        }
        // malloc申请的内存空间默认填充是随机值,需要我们对其清零
        memset(p,0,sizeof(int));// 适合批量赋值0
    
        // 向这块空间赋值
        *p = 100;
    
        // 访问这块空间中的数据
        printf("%d\n",*p);
    
        //内存使用完毕,释放内存
        free(p);
        // 置空,防止产生悬空指针
        p = NULL;
        return 0;
    }
    
  • 注意事项

    • 分配内存后需要手动初始化内存,推荐memstcalloc
    • 内存空间连续,不可越界访问
calloc
  • 头文件 :#include <stdlib>

  • 函数原型:

    void* colloc(size_t nitems,size_t size);
    
  • 功能:动态分配内存,并初始化为0

  • 参数:

    • nitems:元素个数
    • size :每个元素的字节大小
  • 返回值:

    • 成功 :返回内存指针(申请到的内存的首地址)
    • 失败 :失败返回NULL
  • 示例:

/*************************************************************************
  > File Name:    demo03.c
  > Author:       刘孟丹
  > Description:  
  > Created Time: 2025年05月23日 星期五 14时46分56秒
 ************************************************************************/

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

#define LEN 5

int main(int argc,char *argv[])
{
    // 创建一个指针变量,用来接收内存分配后返回的内存地址
    int *arr = calloc(LEN, sizeof(int)); // 分配可以存储5个int数据的内存空间malloc(LEN * sizeof(int)); memset(arr,0,LEN * sizeof(int));

    // 对于指针的使用,一定要进行校验,防止野指针
    if(arr == NULL)
    {
        printf("内存申请失败!\n");
        return -1;
    }
    // 使用for循环快速赋值
    for (int i = 0; i < LEN; i++)
    {
        if (i % 2 == 0)
        {
        continue;
        }
        arr[i] = (i+1)*10; // 0 10 0 ..
    }
    
    // 遍历数组
    int *p = arr;
    for (; p < arr + LEN; p++)
    {
    printf("%d\n", *p);
    }
    //内存使用完毕,释放内存
    free(arr);
    
    // 置空,防止产生悬空指针
    p = NULL;
    return 0;
}
  • 使用场景:为数组分配内存时更安全高效。
realloc
  • 头文件:#include <stdlib.h>

  • 函数原型:

    void* reclloc(void* ptr,sizeof size);
    
  • 功能:调整已分配内存块大小,可能迁移数据到新的地址,扩容后,空余位置是随机值,需要手动清零。

  • 参数:

    • ptr:原内存指针(需要扩容容内存空间的指针,这个ptr的来源(malloc/calloc/relloc))
    • size:指定重新分配内存的大小(字节)
  • 返回值:

    • 成功 :返回内存指针(申请到的内存的首地址)
    • 失败 :失败返回NULL
  • 示例

    /*************************************************************************
      > File Name:    demo03.c
      > Author:       小刘
      > Description:  
      > Created Time: 2025年05月23日 星期五 14时46分56秒
     ************************************************************************/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define LEN 5
    #define NEW_LEN 8
    
    int main(int argc,char *argv[])
    {
        // 创建一个指针变量,用来接收内存分配后返回的内存地址
        int *arr = calloc(LEN,sizeof(int));// 4字符
    
        // 对于指针的使用,一定要进行校验,防止野指针
        if(arr == NULL)
        {
            printf("内存申请失败!\n");
            return -1;
        }
    
        // 使用for 循环快速赋值
        for (int i = 0;i < LEN; i ++)
        {
            if(i % 2 == 0)
            {
                continue;
            }
            arr[i] = (i+1)*10;
        }
    
    
        // 遍历数组
        int *p = arr;
        for(;p < arr + LEN;p++)
        {
            printf("%d\n",*p);
        }
    
        // 扩容至8个int的大小
        int *temp = (int*)realloc(arr,NEW_LEN * sizeof(int));
        // 扩容成功,更新指针
        arr = temp;
        // 对扩容部分清零
        memset(arr+5,0, 3 * sizeof(int));
    
        arr[7] = 666;
    
        // 遍历数组
        for(int i = 0;i < NEW_LEN;i ++)
        {
            printf("%-6d",*(arr+i));
        }
        printf("\n");
        //内存使用完毕,释放内存
        free(arr);
        // 置空,防止产生悬空指针
        p = NULL;
        return 0;
    }
    
  • 注意事项:

    • 分配后需要手动初始化内存,推荐 memset
    • 必须用临时变量接收返回值,避免直接覆盖原指针导致内存泄漏。
    • size 为0,等效于free(ptr)
free
  • 头文件: #include <stdlib.h>
  • 函数原型: void free(void* ptr);
  • 功能:释放动态分配的内存。
  • 注意事项:
    • 只能释放一次,重复释放会导致程序崩溃。
    • 释放后应将指针置为 NULL ,避免野指针。
    • 栈内存由系统自动释放,无需手动free

内存管理【扩展资料】

C进程内存布局

任何一个程序,正常运行都需要内存资源,用来存放诸如变量、常量、函数代码等等。这些不同的内容,所存储的内存区域是不同的,且不同的区域有不同的特性。因此我们需要研究C语言进程的内存布局,逐个了解不同内存区域的特性。

每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程。例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。

  • PM:Physical Memory,物理内存。

  • VM:Virtual Memory,虚拟内存。

    在这里插入图片描述

将其中一个C语言含如进程的虚拟内存放大来看,会发现其内部包下区域:

  • 栈(stack)

  • 堆(heap)

  • 数据段

  • 代码段

    在这里插入图片描述

虚拟内存中,内核区段对于应用程序而言是禁闭的,它们用于存放操作系统的关键性代码,另外由于 Linux 系统的历史性原因,在虚拟内存的最底端 0x0 ~ 0x08048000 之间也有一段禁闭的区段,该区段也是不可访问的。

虚拟内存中各个区段的详细内容:

在这里插入图片描述

栈内存

  • 什么东西存储在栈内存中?

    • 环境变量
    • 命令行参数
    • 局部变量(包含形参)
  • 栈内存有什么特点?

    • 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。
    • 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。
    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。
  • 注意;栈内存的分配和释放,都是由系统规定

    在这里插入图片描述

  • 示例代码:

void func(int a, int *p) // 在函数 func 的栈内存中分配
{
	double f1, f2; // 在函数 func 的栈内存中分配
	... // 退出函数 func 时,系统的栈向上缩减,释放内存
} 

int main(void)
{
	int m = 100; // 在函数 main 的栈内存中分配
	func(m, &m); // 调用func时,系统的栈内存向下增长
}

静态数组

C语言中,静态数据有两种:

  • 全局变量:定义在函数外部的变量。
  • 静态局部变量:定义在函数内部,且被static修饰的变量。
  • 示例:
int a; // 全局变量,退出整个程序之前不会释放
void f(void)
{
	static int b; // 静态局部变量,退出整个程序之前不会释放
	printf("%d\n", b);
	b++;
}

int main(void)
{
	f();
	f(); // 重复调用函数 f(),会使静态局部变量 b 的值不断增大
}
  • 为什么需要静态数据?

    1. 全局变量在默认的情沉下,对所有文件可见,为某些需要在各个不同文件和函数间访问的数据提供操作上的方便。
    2. 当我们希望一个函数退出后依然能保留局部变量的值,以便于下次调用时还能用时,静态局部变量可帮助实现这样的功能。
  • 注意1:

    • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0
    • 静态数据初始化语句,只会执行一遍。
    • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。
  • 注意2:

    • static修饰局部变量:使之由栈内存临时数据,变成了静态数据。
    • static修饰全局变量:使之由各文件可见的静态数据,变成了本文件可见的静态数据。
    • static修饰函数:使之由各文件可见的函数,变成了本文件可见的静态函数。

数据段与代码段

  • 数据段细分成如下几个区域

    • .bss段:存放未初始化的静态数据,它们将被系统自动初始化为0
    • .data段:存放已初始化的静态数据
    • .rodata段:存放常量数据
  • 代码段细分如下几个区域:

    • .text段:存放用户代码

    • .init段:存放系统初始化代码

      在这里插入图片描述

int a; // 未初始化的全局变量,放置在.bss 中
int b = 100; // 已初始化的全局变量,放置在.data 中

int main(void)
{
	static int c; // 未初始化的静态局部变量,放置在.bss 中
	static int d = 200; // 已初始化的静态局部变量,放置在.data 中
    
	// 以上代码中的常量100、200防止在.rodata 中
}
  • 注意:数据段和代码段内存的分配和释放,都是由系统规定的,我们无法干预。

堆内存

堆内存(heap)又被称为动态内存、自由内存,简称堆。堆是唯一可被开发者自定义的区段,开发者可以根据需要申请内存的大小、决定使用的时间长短等。但又由于这是一块系统“飞地”,所有的细节均由开发者自己把握,系统不对此做任何干预,给予开发者绝对的“自由”,但也正因如此,对开发者的内存管理提出了很高的要求。对堆内存的合理使用,几乎是软件开发中的一个永恒的话题。

  • 堆内存基本特征:

    • 相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。

    • 相比栈内存,堆内存从下往上增长。

    • 堆内存是匿名的,只能由指针来访问

    • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。

      在这里插入图片描述

  • 相关API:

    • 申请堆内存:malloc() / calloc()

    • 清零堆内存:bzero()

    • 释放堆内存:free()

      在这里插入图片描述

  • 示例:

    int *p = malloc(sizeof(int)); // 申请1块大小为 sizeof(int) 的堆内存
    bzero(p, sizeof(int)); // 将刚申请的堆内存清零
    
    *p = 100; // 将整型数据 100 放入堆内存中
    free(p); // 释放堆内存
    
    // 申请3块连续的大小为 sizeof(double) 的堆内存
    double *k = calloc(3, sizeof(double));
    
    k[0] = 0.618;
    k[1] = 2.718;
    k[2] = 3.142;
    free(k); // 释放堆内存
    
  • 注意:

    • malloc()申请的堆内存,默认情况下是随机值,一般需要用 bzero() 来清零。
    • calloc()申请的堆内存,默认情况下是已经清零了的,不需要再清零。
    • free()只能释放堆内存,并且只能释放整块堆内存,不能释放别的区段的内存或者释放一部分堆内存。
  • 释放内存的含义:

    • 释放内存意味着将内存的使用权归还给系统。

    • 释放内存并不会改变指针的指向。
      受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。

    • 相比栈内存,堆内存从下往上增长。

    • 堆内存是匿名的,只能由指针来访问

    • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。

      [外链图片转存中…(img-uJZRjUr3-1748523359012)]

  • 相关API:

    • 申请堆内存:malloc() / calloc()

    • 清零堆内存:bzero()

    • 释放堆内存:free()

      [外链图片转存中…(img-MG8vz7Fm-1748523359012)]

  • 示例:

    int *p = malloc(sizeof(int)); // 申请1块大小为 sizeof(int) 的堆内存
    bzero(p, sizeof(int)); // 将刚申请的堆内存清零
    
    *p = 100; // 将整型数据 100 放入堆内存中
    free(p); // 释放堆内存
    
    // 申请3块连续的大小为 sizeof(double) 的堆内存
    double *k = calloc(3, sizeof(double));
    
    k[0] = 0.618;
    k[1] = 2.718;
    k[2] = 3.142;
    free(k); // 释放堆内存
    
  • 注意:

    • malloc()申请的堆内存,默认情况下是随机值,一般需要用 bzero() 来清零。
    • calloc()申请的堆内存,默认情况下是已经清零了的,不需要再清零。
    • free()只能释放堆内存,并且只能释放整块堆内存,不能释放别的区段的内存或者释放一部分堆内存。
  • 释放内存的含义:

    • 释放内存意味着将内存的使用权归还给系统。
    • 释放内存并不会改变指针的指向。
    • 释放内存并不会对内存做任何修改,更不会将内存清零

网站公告

今日签到

点亮在社区的每一天
去签到