C语言基础13(内存管理)

文章目录

常量指针和指针常量

常量:分为字面量和只读常量,字面量就是我们平时直接操作的量:printf("%d\n",12);printf("%s\n","hello");只读常量,使用关键字const修饰,凡是被这个关键字修饰的变量,一旦赋值,值就不能改变

c 复制代码
//字面量
printf("%d\n",12);

//只读常量
const int a = 10;
a = 21;//编译错误,因为此时这个变量是只读常量,所以不可更改其值

常量指针

定义:常量的指针,本质是一个指针,指向的数据不能改变

定义格式:

c 复制代码
const 数据类型 *变量名;

举例:

c 复制代码
const int *p;//p就是常量指针

结论

1.常量指针指向的数据不能被改变(不能解引用修改数据)

2.常量指针的地址可以改变(指向是可以改变)

应用场景:作为形式参数,实际参数需要给一个常量

c 复制代码
void arr(int *array,int len){.. }
c 复制代码
#include <stdio.h>

int main(int argc,char *argv[])
{
    //定义变量
    int a = 10;

    //定义常量指针
    const int *p = &a;

   // * p = 100;//编译报错,常量的值不能被改变
    printf("%d\n",*p);

    //定义变量
    int b = 20;
    p = &b;  //编译通过,常量的地址可以被改变
    printf("%d\n",*p);//20

    return 0;
}

指针常量

定义:指针的常量,指针的指向不能改变

定义格式:

c 复制代码
数据类型* const 变量名:

举例:

c 复制代码
int* const p;//指针常量

结论:

1.指针常量的指向不能改变(不能给指针变量重新赋地址值)

2.指针常量的指向的数据可以改变

注意:指针常量在定义时就要赋值;不能先定义后赋值,否则编译报错

常量指针常量

定义语法:

c 复制代码
const 数据类型* const 变量名;

举例:

c 复制代码
const int* const p;

作用:p的指向不能被改变(地址不可更改),p指向的数据不能改变(地址对应的数据不可更改)

数据段包含常量池

栈内存

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

  • 环境变量

  • 命令行参数

  • 局部变量(包括形参)

  • 栈内存有什么特点?

    • 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。

    • 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。

    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。

  • 注意:

栈内存的分配和释放,都是由系统规定的,我们无法干预

静态数据

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

  • 全局变量:定义在函数外部的变量。

  • 静态局部变量:定义在函数内部,且被static修饰的变量。

示例:

c 复制代码
int a; // 全局变量,退出整个程序之前不会释放
void f(void)
{
static int b; // 静态局部变量,退出整个程序之前不会释放
printf("%d\n", b);
b++;
}
int main(void)
{
f();
f(
  • 为什么需要静态数据?
  1. 全局变量在默认的情况下,对所有文件可见,为某些需要在各个不同文件和函数间访问的数据提供操作上的方便。

  2. 当我们希望一个函数退出后依然能保留局部变量的值,以便于下次调用时还能用时,静态局部变量可帮助实现这样的功能。

  • 注意1:

  • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0

  • 静态数据初始化语句,只会执行一遍。

  • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。

  • 注意2:

    • static修饰局部变量:使之由栈内存临时数据,变成了静态数据。

    • static修饰全局变量:使之由各文件可见的静态数据,变成了本文件可见的静态数据。

    • static修饰函数:使之由各文件可见的函数,变成了本文件可见的静态函数。

数据段与代码段

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

    • .bss 段:存放未初始化的静态数据,它们将被系统自动初始化为0

    • .data段:存放已初始化的静态数据

    • .rodata段:存放常量数据

  • 代码段细分成如下几个区域:

    • .text段:存放用户代码

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

  • 注意:数据段和代码段内存的分配和释放,都是由系统规定的,我们无法干预。

堆内存

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

  • 堆内存基本特征:

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

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

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

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

  • 相关API:

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

    • 清零堆内存:bzero()

    • 释放堆内存:free()

动态内存分配

我们要想实现动态内存分配,就需要学习标准C提供的函数库(API):

1.函数所属的库文件

2.函数的原型-函数的声明

  • 函数名
  • 形参
  • 返回值类型

3.函数功能

注意:内存分配函数在申请内存时,建议用多少申请多少,可以有少量的预留量;但不能越界访问(虽然编译和运行不报错,但是数据不安全(野指针))

常用函数

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

  • 函数功能:C库函数void* malloc(size_t size);分配所需的内容空间,并返回一个指向它的指针

  • 函数原型:

    • 函数名:malloc
    • 形式参数:size_t size:需要申请的内存块的大小,以字节为单位。本质上是一个unsigned long int
    • 返回值类型:void* (万能指针):该函数返回一个指针,指向已分配大小的内存,如果请求失败,返回NULL(0x0000000000对应的一块不可访问的区域)
    • 举例:
c 复制代码
int* p =(int*)malloc(4);
//清零,这里不是释放内存,只是将内存中的随机值清理掉
bzero(p,sizeof(int));
//使用空间..
//释放空间
free(p);
  • 说明:
    • malloc函数分配的内存没有默认值,内存中的数据时随机值(大概率是0),使用前需要借助于bzero()清零
    • malloc函数申请的内存空间连续0,4.,8
calloc
  • 头文件:#include <stdlib.h>
  • 函数功能:C库函数void* calloc(size_t nitems,size_t size)分配所需的内存空间,并返回一个指向它的指针

malloc和calloc之间的区别:malloc不会设置内存为零,需要使用bzero()清零,而calloc会设置内存为零

  • 函数原型:
    • 函数名:calloc
    • 形式参数:
      • size_t nitems:申请多少个
      • size_t size:一个占几个内存单元(一个内存单元等于一个字节)
    • 返回值类型:void* :该函数返回一个指针,指向已分配大小的内存,如果请求失败,返回NULL
  • 说明:
    • calloc函数分配的内存有默认值,每个内存单元都是0
    • calloc函数申请的内存空间连续
    • calloc大多时候为数组中的元素申请内存
  • 案例:
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

/**
 *需求:转存栈中数组中的数据
 *
*/


int main(int argc,char *argv[])
{
    //在栈区创建一个数组
    int arr[3] = {11,22,33};

    //在堆区申请内存
    int *p =(int*)calloc(3,sizeof(int));

    //转存
    for(int i = 0;i < 3;i++)
    {
        p[i] = arr[i];
    }

    //遍历
    for(int i = 0;i < 3;i++)
    {
        printf("%-3d ",p[i]);
    }
    printf("\n");

    //在堆中申请空间,使用后要释放内存
    free(p);
    p = NULL;
    return 0;
}
realloc
  • 头文件: #include <stdlib.h>

  • 函数功能:尝试重新调整之前调用malloc或calloc所分配的ptr所指向的内存块的大小。

  • 函数原型: void *realloc(void *ptr,size_t size)

    • 函数名:realloc

    • 形式参数:

      • void *ptr:是malloc或者calloc的返回值

      • size_t size:重新分配后的内存大小

    • 返回值:void*:该函数返回一个指针,指向已分配大小的内存。如果请求失败,返回NULL

  • 案例:

c 复制代码
int *p = (int*)malloc(4);
int *w = (int*)realloc(p,20);
// int *q = (int*)realloc(p,0); // 等效于free(p)
  • 说明:
  1. realloc以原来malloc返回的内存地址开始,分配总共20个字节的内存空间

  2. 如果原来的内存空间后有20个连续空间,就扩容20-4 =16个内存单元,返回原来旧的内存首地址。

  3. 如果原来的内存空间后不够20个连续内存空间,就重新找一个内存地址开始,申请20个内存单元。并将原来的数据拷贝到新的内存中,回收旧的内存单元,并返回新的内存首地址

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

  • 函数功能:释放之前调用 malloc、calloc、realloc所分配的内存空间,是访问完记得使用NULL置空。

  • 函数原型: void free(void *ptr)

    • 函数名:free

    • 形式参数:

      • void *ptr:calloc,malloc.realloc的返回值
    • 返回值类型:void:没有返回值

  • 注意:

  1. 堆内存中的指针才需要回收,栈中系统会自动回收
  2. 堆内存不能重复回收,运行会报错

说明:

  1. 堆的内存空间相比较栈要大很多
  2. 内存分配函数返回的指针变量可以参与运算(只读),但不能被修改(p++或者p+=i 是错误的)

附表

相关推荐
廋到被风吹走1 分钟前
【Java】【JVM】OOM 原因、定位与解决方案
java·开发语言·jvm
MSTcheng.4 分钟前
【C++STL】map / multimap 保姆级教程:从底层原理到实战应用!
开发语言·c++·stl·map·红黑树
csbysj20206 分钟前
Bootstrap5 按钮组
开发语言
kaikaile19957 分钟前
使用纯MATLAB M函数实现的无刷直流电机控制系统仿真
开发语言·matlab
崇山峻岭之间10 分钟前
Matlab学习记录09
开发语言·学习·matlab
wjs202412 分钟前
Python XML 解析
开发语言
小白学大数据14 分钟前
Temu 商品历史价格趋势爬虫与分析
开发语言·javascript·爬虫·python
走在路上的菜鸟14 分钟前
Android学Dart学习笔记第二十五节 类修饰符
android·笔记·学习·flutter
帮帮志15 分钟前
启动phcharm报错:Archived non-system classes are disabled because the java.system.
java·开发语言
秦苒&16 分钟前
【C语言指针五】转移表、回调函数、qsort、qsort函数的模拟实现
c语言·开发语言·c#