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 是错误的)

附表

相关推荐
吃着火锅x唱着歌9 分钟前
PHP7内核剖析 学习笔记 第三章 数据类型
android·笔记·学习
keira67410 分钟前
【21天学习AI底层概念】day5 机器学习的三大类型不能解决哪些问题?
人工智能·学习·机器学习
宽广22 分钟前
java aspose word 模板根据数据导出pdf
java·开发语言·pdf·c#·word
数据小爬虫@1 小时前
利用Python爬虫技术获取商品销量详情
开发语言·爬虫·python
字节高级特工1 小时前
C++---入门
开发语言·c++·算法
北辰浮光2 小时前
[JavaScript]Ajax-异步交互技术
开发语言·javascript·ajax
亦是远方2 小时前
C++编译过程
java·开发语言·c++
程序猿000001号2 小时前
使用Python的Turtle库绘制图形:初学者指南
开发语言·python
carlcarl09032 小时前
qt通过QAxObject操作word插入表格及数据,图片生成文档
开发语言·c#