[C][动态内存分配][柔性数组]详细讲解

目录

  • 1.动态内存函数的介绍
    • 1.malloc
    • 2.free
    • 2.calloc
    • 4.realloc
  • 2.常见的动态内存错误
  • 3.C/C++程序的内存开辟
  • 4.柔性数组
    • 1.是什么?
    • 2.柔性数组的特点
    • 3.柔性数组的使用
    • 4.柔性数组的优势


1.动态内存函数的介绍

1.malloc

  • 函数原型void* malloc(size_t size)
  • 功能
    • malloc()向内存申请一块连续可用的空间,并返回指向这块空间的指针
  • 返回值
    • 如果开辟成功,则返回一个指向开辟好空间的指针
    • 如果开辟失败,则返回一个NULL指针
      • 因此malloc的返回值一定要做检查
  • 返回值的类型是void*,所以**malloc()并不知道开辟空间的类型**,具体在使用的时候使用者自己来决定(强制类型转换)
  • 如果参数size == 0malloc()的行为是标准是未定义的,取决于编译器

2.free

  • 函数原型void free(void* ptr)
  • 功能free()用来释放动态开辟的内存
  • 如果参数ptr指向的空间不是动态开辟的,那free()的行为是未定义的
  • 如果参数ptrNULL,则函数什么事都不做
  • 一次完整的动态内存开辟的例子
int* p = (int*)malloc(10 * sizeof(int));
if(p == NULL)
{
	perror("main"); // main: xxxx
	return 0;
}

// 使用
for(int i = 0; i < 10; i++)
{
	*(p + i) = i;
}

free(p); // 回收空间
p = NULL; // 手动把p置为NULL

2.calloc

  • 函数原型void* calloc(size_t num, size_t size)
  • 功能:为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
  • 与函数malloc()的区别只在于calloc()会在返回地址之前把申请的空间的每个字节初始化为全0
  • 如果对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务

4.realloc

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

  • 功能

    • realloc()的出现让动态内存管理更加灵活
    • 有时会发现过去申请的空间太小了,有时候又会觉得申请的空间过大了,一定会对内存的大小做灵活的调整,realloc()就可以做到对动态开辟内存大小的调整
  • 参数

    • ptr:要调整的内存地址
      • ptr == NULL,则realloc()作用同malloc()
    • size:调整之后新大小
  • 返回值:调整之后的内存起始位置

  • 注意:这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

  • realloc()在调整内存空间时,存在两种情况

    1. 原有空间之后有足够大的空间
      • 要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化
    2. 原有空间之后没有足够大的空间
      • 扩展方法
        • 在堆空间上另找一个合适大小的连续空间来使用
        • 这样函数返回的是一个新的内存地址
    • 由于上述的两种情况,realloc()的使用就要注意一些
      请添加图片描述
  • 例如:为了确保内存空间成功开辟,拿一个临时指针去接收新开辟的空间的地址,再赋值,这样保证了万一没有成功开辟新的地址,而丢失了原来的地址

int* p =int*)calloc(10, sizeof(int));
if(p == NULL)
{
	perror("main");
	return 1;
}

for(int i = 0; i < 10; i++) // 使用
{
	*(p + i) = i;
}

// 这里需要p指向更大空间,realloc调整
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if(ptr != NULL)
{
	p = ptr;
}

free(p);
p = NULL;

2.常见的动态内存错误

  1. 对NULL指针的解引用操作
  2. 对动态开辟空间的越界访问
  3. 使用free释放非动态开辟的空间
  4. 使用free释放动态内存中的一部分
  5. 对同一块动态开辟的空间,多次释放
  6. 动态开辟的空间忘记释放- 内存泄漏 -> 比较严重
  • 经典例子一
    • str传给GetMemory()的时候是值传递,所以GetMemory()的形参pstr的一份临时拷贝
    • GetMemory()内部动态申请空间的地址,存放在p中,不会影响外边的str,所以当GetMemory()返回之后,str依然是NULL,所以strcpy()会失败
    • GetMemory()返回之后,形参p销毁,使得动态开辟的100个字节存在内存泄漏无法释放
void GetMemory(char* p)
{
    p = (char *)malloc(100);
}

void Test(void)
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
}

int main()
{
    Test();
    return 0;
}

// 改进一
char* GetMemory(char* p)
{
    p = (char *)malloc(100);
    return p;
}

void Test(void)
{
    char* str = NULL;
    str = GetMemory(str);
    strcpy(str, "SnowK");
    free(str);
    str = NULL:
}

// 改进二
void GetMemory(char** p)
{
    *p = (char *)malloc(100);
}

void Test(void)
{
    char* str = NULL;
    GetMemory(&str);
    strcpy(str, "SnowK");
    free(str);
    str = NULL:
}
  • 经典例子二
    • GetMemory()内部创建的数组是在栈区上创建的
    • 出了函数,p[]的空间就还给了操作系统
    • 返回的地址是没有实际的意义,如果通过返回的地址去访问内存,就是非法访问内存
char* GetMemory()
{
	char p[] = "SnowK";
	return p;
}

void Test()
{
	char* str = NULL;
	str = GetMemory();
}

3.C/C++程序的内存开辟

  • C/C++程序内存分配的几个区域:
    1. 栈区(stack)
      • 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放
      • 栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限
      • 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等
    2. 堆区(heap)
      • 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收
      • 分配方式类似于链表
    3. 数据段(静态区)(static)
      • 存放全局变量、静态数据程序
      • 结束后由系统释放
    4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码
      • 实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁
      • 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长
        请添加图片描述

4.柔性数组

1.是什么?

struct s
{
	int n;
	int arr[]; // 大小是未知
}

2.柔性数组的特点

  • 结构中的柔性数组成员前面必须至少一个其他成员
  • sizeof返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
typedef struct st_type
{
	int i;
	int arr[0]; // 柔性数组成员
}type_a;

printf("%d\n", sizeof(type_a)); // 输出的是4

3.柔性数组的使用

// 期望arr的大小是10个整形
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
ps->n = 10;

for(int i = 0; i < 10; i++)
{
	ps->arr[i] = i;
}

// 增加
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
if(ptr != NULL)
{
	ps = ptr;
}

// 使用
// ...
// 释放
free(ps);
ps = NULL;

4.柔性数组的优势

  • 方便内存释放
    • 如果代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户
    • 用户调用free()可以释放结构体,但是用户并不知道这个结构体内的成员也需要free(),所以你不能指望用户来发现这个事
    • 所以,如果把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free()就可以把所有的内存也给释放掉
  • 这样有利于访问速度
    • 连续的内存有益于提高访问速度,也有益于减少内存碎片
      • 其实,个人觉得也没高多少,反正也避免不了要用做偏移量的加法来寻址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/657960.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux网络编程:应用层协议|HTTP

前言&#xff1a; 我们知道OSI模型上层分为应用层、会话层和表示层&#xff0c;我们接下来要讲的是主流的应用层协议HTTP&#xff0c;为什么需要这个协议呢&#xff0c;因为在应用层由于操作系统的不同、开发人员使用的语言类型不同&#xff0c;当我们在传输结构化数据时&…

前端开发工程师——AngularJS

一.表达式和语句 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-w…

AI播客下载:Acquired podcast每个公司都有一个故事

"Acquired Podcast" 是一档专注于深度解析科技行业和企业发展历程的播客节目&#xff0c;由Ben Gilbert和David Rosenthal主持。其口号是&#xff1a;Every company has a story.《Acquired》每一集都围绕一个特定的主题或公司进行讨论。它以独特的视角和深入的分析&…

FFmpeg编解码的那些事(1)

看了网上很多ffmpeg的编解码的文章和代码&#xff0c;发现有很多文章和代码都过时了&#xff0c;主要还是ffmpeg有很多接口都已经发生变化了。 这里简单说一下&#xff0c;什么是编码和解码。 1.视频编码 对于视频来说&#xff0c;可以理解为多张&#xff08;rgb或者yuv&…

Numba 的 CUDA 示例(1/4):踏上并行之旅

按照本系列从头开始使用 Python 学习 CUDA 编程 介绍 GPU&#xff08;图形处理单元&#xff09;&#xff0c;顾名思义&#xff0c;最初是为计算机图形学开发的。从那时起&#xff0c;它们几乎在每个需要高计算吞吐量的领域都无处不在。这一进步得益于 GPGPU&#xff08;通用 G…

从零开始学React--环境搭建

React官网 快速入门 – React 中文文档 1.搭建环境 下载nodejs,双击安装 nodejs下载地址 更新npm npm install -g npm 设置npm源&#xff0c;加快下载速度 npm config set registry https://registry.npmmirror.com 创建一个react应用 npx create-react-app react-ba…

React-组件基础使用

组件是什么 概念&#xff1a;一个组件就是用户界面的一部分&#xff0c;它可以有自己的逻辑和外观&#xff0c;组件之间可以互相嵌套&#xff0c;也可以复用多次 组件化开发可以让开发者像搭积木一样构建一个完整的庞大的应用 React组件 在React中&#xff0c;一个组件就是首…

Rocksdb原理简介

100编程书屋_孔夫子旧书网 Rocksdb作为当下nosql中性能的代表被各个存储组件&#xff08;mysql、tikv、pmdk、bluestore&#xff09;作为存储引擎底座&#xff0c;其基于LSM tree的核心存储结构&#xff08;将随机写通过数据结构转化为顺序写&#xff09;来提供高性能的写吞吐时…

读人工智能时代与人类未来笔记17_人类未来

1. 人类未来 1.1. 在印刷读物出现之前&#xff0c;中世纪的欧洲人主要通过社区传统获取知识 1.1.1. 通过参与收割和季节轮作积累民间智慧 1.1.2. 在礼拜场所践行信仰并遵守圣礼 1.1.3. 加入行业公会&#xff0c;学习技术&…

重置服务器之后 SSH 登录报错:REMOTE HOST IDENTIFICATION HAS CHANGED!

问题原因&#xff1a; 报错是由于远程的主机的公钥发生了变化导致的。ssh服务是通过公钥和私钥来进行连接的&#xff0c;它会把每个曾经访问过计算机或服务器的公钥&#xff08;public key&#xff09;&#xff0c;记录在~/.ssh/known_hosts 中&#xff0c;当下次访问曾经访问…

量子密钥分发系统基础器件(一):光纤干涉仪

干涉仪的基本原理是利用波的叠加来获得波的相位信息&#xff0c;从而获取实验中所关心的物理量。光纤干涉仪是由光学干涉仪发展而来的&#xff0c;利用光纤实现光的干涉&#xff0c;由于光纤取代透镜系统构成的光路具有柔软、形状可随意变化、传输距离远等特点&#xff0c;当前…

MouseBoost Pro for Mac v3.4.7 鼠标右键助手 安装教程【支持M芯片】

MouseBoost Pro for Mac v3.4.7 鼠标右键助手 安装教程【支持M芯片】 原文地址&#xff1a;https://blog.csdn.net/weixin_48311847/article/details/139201501

电源模块测试系统怎么测试输入电压范围?

在现代电子设备中&#xff0c;电源模块的性能直接影响着整个系统的稳定性和效率。其中&#xff0c;电源输入电压范围是指电源能够接受的输入电压的最小值和最大值&#xff0c;它是确保电源正常工作的重要参数。为了提高测试效率和精度&#xff0c;自动化的测试方法逐渐取代了传…

《软件方法(下)》8.3.4.6 DDD话语“聚合”中的伪创新(1)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.3 建模步骤C-2 识别类的关系 8.3.4 识别关联关系 8.3.4.6 DDD话语“聚合”中的伪创新 DDD话语中也有“聚合”。Eric Evans的“Domain-Driven Design: Tackling Complexity in the…

04--SpringBoot热部署与日志

1、热部署 1.1 引言 为了进一步提高开发效率&#xff0c;SpringBoot为我们提供了全部项目热部署&#xff0c;日后在开发过程中修改了部分代码或者相关配置文件之后&#xff0c;不需要再重启服务使其生效。在项目中开启了全局热部署之后&#xff0c;只需要在修改之后等待几秒钟…

气密检测中泄漏率的质量流量与体积流量的转换

对于R-134a等制冷剂&#xff0c;泄漏率通常表示为质量流量&#xff08;每年的逸出质量&#xff09;而不是体积流量&#xff08;特定时间段内给定压力下的逸出质量&#xff09;。因此&#xff0c;通过制冷剂的年泄漏量来定义泄漏级别&#xff0c;常用的单位为g/a。以某款车型为例…

Github 2024-05-29 C开源项目日报 Top10

根据Github Trendings的统计,今日(2024-05-29统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量C项目10C++项目3PHP项目1PHP:流行的Web开发脚本语言 创建周期:4710 天开发语言:C, PHP协议类型:OtherStar数量:37340 个Fork数量:7657 次…

从 0 手撸一个 pytorch

背景介绍 最近抽空看了下 Andrej Karpathy 的视频教程 building micrograd&#xff0c;教程的质量很高。教程不需要任何前置机器学习基础&#xff0c;只需要有高中水平的数学基础即可。整个教程从 0 到 1 手撸了一个类 pytorch 的机器学习库 micrograd&#xff0c;核心代码不到…

SAP PP学习笔记 - 错误 CX_SLD_API_EXCEPTION - Job dump is not fully saved (too big)

我这个错误是跑完MRP&#xff0c;然后在MD04查看在库/所有量一览&#xff0c; 点计划手配&#xff08;Planned order 计划订单&#xff09;生成 制造指图&#xff08;Production order 生产订单&#xff09;&#xff0c; 到目前这几步都OK&#xff0c;然后在制造指图界面点保…

Linux之sshpass命令

介绍 sshpass是一个工具&#xff0c;用于通过SSH连接到远程服务器时自动输入密码。它允许您在命令行中指定密码&#xff0c;以便在建立SSH连接时自动进行身份验证。 安装 # 以centos为例 yum install sshpass -y 使用方法 sshpass [-f filename | -d num | -p password | …