重生之我在异世界学编程之C语言:深入动态内存管理收尾 + 柔性数组篇

news2024/12/24 23:50:56

大家好,这里是小编的博客频道
小编的博客:就爱学编程

很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!!

本文目录

  • 引言
  • 正文
    • 常见的动态内存管理错误
      • (1)对指针的越界访问
      • (2)free函数对非堆区的指针的释放是非法的
      • (3)使用free函数释放动态内存开辟空间的一部分
      • (4)对于同一块动态内存释放了一次以上
      • (5)内存泄漏(未使用free释放不再使用的动态内存)
    • 柔性数组
      • 一 柔性数组的定义与特性
      • 二 柔性数组的用法
      • 三 柔性数组的大小计算
      • 三 柔性数组的优势与应用场景
      • 四 注意事项与常见问题
      • 五 总结与展望
  • 快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!!

引言

本文是小编承接上一篇——《重生之我在异世界学编程之C语言:深入动态内存管理篇》所作的收尾和补充。逻辑上存在一定的先后顺序,建议先搞懂之前的知识点再进行本章内容的学习。那现在宝子们就跟着小编的步伐一起进入本章知识的学习。Go!Go!Go!

在这里插入图片描述


那接下来就让我们开始遨游在知识的海洋!

正文


常见的动态内存管理错误

温馨提示:

  • 本节内容所展示的代码都为错误代码,宝子们可以根据提示进行自查。

(1)对指针的越界访问

常见于:对于malloc函数的参数理解错误

例:



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

int main(){
	int* p = (int*)malloc(100);
	if (p == NULL) {
		perror("malloc");
	}
	for (int i = 0; i < 100; i++) {
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}


(2)free函数对非堆区的指针的释放是非法的

常见于:对于free函数理解错误

例:

#include<stdio.h>
int main() {
	int arr[10] = { 0 };
	int* p = arr;
	free(p);
	p = NULL;
}

(3)使用free函数释放动态内存开辟空间的一部分

常见于:对于free函数理解错误

例:

#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 0;
	}	
	p++;
	free(p);
	p = NULL;
	return 0;
}

(4)对于同一块动态内存释放了一次以上

常见于:对于free函数理解错误

例:

#include<stdio.h>
int main(){
	int* p = (int*)malloc(10 * sizeof(int));
	int i = 0;
	for (i = 0; i < 10; i++) {
		*(p + i) = i;
	}
	free(p);
	//.....
	free(p);
	p = NULL;
	return 0;
}

(5)内存泄漏(未使用free释放不再使用的动态内存)

常见于:对于free函数不会运用

例:

void test() {
	int* p = (int*)malloc(10 * sizeof(int));
	int i = 0;
	for (i = 0; i < 10; i++) {
		*(p + i) = i;
	}
	return p;
}
#include<stdio.h>
int main() {
	int* p = (int*)malloc(10 * sizeof(int));
	int i = 0;
	for (i = 0; i < 10; i++) {
		*(p + i) = i;
	}
	//未使用free释放p指向的内存会导致内存的泄露
	p = NULL;
	return 0;
	//吃内存的程序
	while (1) {
		void* p = malloc(1);
		//未使用free释放p指向的内存会导致内存的泄露
	}

}

在C语言编程中,结构体(struct)是一种非常重要的数据类型,它允许我们将多个不同类型的数据组合在一起,形成一个复合类型。然而,在某些情况下,我们可能希望结构体的某个成员能够具有可变的大小,以适应不同的数据需求。为了满足这一需求,C99标准引入了柔性数组的概念

柔性数组

一 柔性数组的定义与特性

1. 定义

  • 柔性数组是结构体中的一个特殊成员,它的类型是未指定大小的数组。换句话说,这个数组在定义时没有给出具体的长度,而是留待后续使用时动态确定这种设计使得结构体可以灵活地适应不同大小的数据块

2. 特性

  • 灵活性:柔性数组允许结构体根据实际需要动态调整大小,从而提高了代码的灵活性和可重用性
  • 内存连续性:由于柔性数组紧跟在结构体的其他成员之后,因此可以保证数据的内存连续性,这对于某些需要连续访问数据的算法来说是非常有利的
  • 限制:柔性数组必须是结构体的最后一个成员,且只能有一个柔性数组成员。这是因为编译器需要在编译时知道结构体的大小,以便进行内存分配和访问。如果柔性数组不是最后一个成员或者存在多个柔性数组成员,那么编译器将无法确定结构体的大小

二 柔性数组的用法

1. 基本用法

下面是一个简单的例子,展示了如何使用柔性数组来创建一个可变大小的结构体:

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

typedef struct {
    int id;
    char name[20];
    // 柔性数组,用于存储可变长度的数据
    char data[];
} MyStruct;

MyStruct* create_mystruct(const char* name, const char* data) {
    size_t datalen = strlen(data) + 1; // 包括终止符'\0'
    size_t structsize = sizeof(MyStruct) - sizeof(char) + datalen; // 计算结构体总大小

    // 使用malloc分配内存
    MyStruct* p = (MyStruct*)malloc(structsize);
    if (!p) {
        perror("Failed to allocate memory");
        exit(EXIT_FAILURE);
    }

    p->id = 1; // 设置ID
    strncpy(p->name, name, sizeof(p->name)); // 复制名称到结构体中
    strncpy(p->data, data, datalen); // 复制数据到柔性数组中

    return p;
}

void free_mystruct(MyStruct* p) {
    free(p);
}

int main() {
    const char* name = "Alice";
    const char* data = "This is a test string.";

    MyStruct* s = create_mystruct(name, data);

    printf("ID: %d
", s->id);
    printf("Name: %s
", s->name);
    printf("Data: %s
", s->data);

    free_mystruct(s);

    return 0;
}
  • 在这个例子中,MyStruct结构体包含了一个柔性数组data,用于存储可变长度的字符串数据。create_mystruct函数根据输入的名称和数据计算结构体所需的总大小,并使用malloc函数分配相应的内存空间。然后,它将输入的数据复制到结构体中并返回指向该结构体的指针。最后,在main函数中,我们创建了一个MyStruct实例并打印出其内容。使用完毕后,通过调用free_mystruct函数释放分配的内存。

2.与指针的比较

虽然柔性数组看起来类似于在结构体中使用字符指针来指向动态分配的数据块,但它们之间有一些重要的区别:

  • 内存连续性:如前所述,柔性数组保证了数据的内存连续性,而指针则不一定。当使用指针时,数据块可能位于内存的任何位置,这可能导致缓存未命中率的增加和性能下降
  • 内存管理使用柔性数组时,整个结构体(包括柔性数组部分)通常是通过单个malloc或类似函数一次性分配的。这意味着我们可以更容易地管理内存,因为只需要一个free调用就可以释放整个结构体。相比之下,如果使用指针来指向动态分配的数据块,则需要分别管理这些块的内存分配和释放

例:

柔性数组实现柔性

typedef struct stu {
	int a;
	char arr[0];    
    //char arr[];   这两种写法都是C99语法规定内的,编译器会支持至少一种写法
}stu;
int main() {
	stu* p = (stu*)malloc(4 * sizeof(int) + 10 * sizeof(char));
	if (p == NULL) {
		perror("malloc::");
		return 1;
	}
	//使用
	int i = 0;
	//赋值
	for (i = 0; i < 10; i++) {
		(p->arr)[i] = i;
	}
	//打印
	for (i = 0; i < 10; i++) {
		printf("%d ", p->arr[i]);
	}
	//释放内存
	free(p);
	p = NULL;
}

非柔性数组实现柔性

typedef struct stu {
		int a;
		//char arr[0];    
		char* arr;
	}stu;
	int main() {
		//第一次申请动态内存
		stu* p = (stu*)malloc(sizeof(int));
		if (p == NULL) {
			perror("malloc::");
			return 1;
		}
		p->a = 100;
		//第二次申请动态内存
		char* pa = (char*)realloc(NULL, 10);
		if (pa == NULL) {
			perror("realloc->arr");
			return 1;
		}
		p->arr = pa;
		pa = NULL;
		//使用
		int i = 0;
		//赋值
		for (i = 0; i < 10; i++) {
			p->arr[i] = 'a';
		}
		//打印
		for (i = 0; i < 10; i++) {
			printf("%c ", p->arr[i]);
		}
		//第一次释放内存
		free(p->arr);
		p->arr = NULL;
		//第二次释放内存
		free(p);
		p = NULL;
	}
	


所以我们通过对比可以得出使用柔性数组的好处有:

  • 1.方便释放内存。
  • 2.减少系统碎片,加快访问速度。

三 柔性数组的大小计算

基本规则

  • 计算含有柔性数组(成员)的结构体的大小不包括柔性数组成员的大小,所以含有柔性数组(成员)的结构体至少有除了柔性数组外的1个成员

例:

typedef struct stu {
	int a;
	char arr[0];    
    //char arr[];   这两种写法都是C99语法规定内的,编译器会支持至少一种写法
}stu;
int main() {
	printf("%zd\n", sizeof(stu));   
} 

运行结果:

在这里插入图片描述

由此我们就能验证以上规则的正确性。


三 柔性数组的优势与应用场景

1. 优势

  • 简化内存管理:通过使用柔性数组,我们可以将数据和结构 体一起分配和管理,从而简化了内存管理的复杂性。
  • 提高性能:由于柔性数组保证了数据的内存连续性,因此可以提高某些算法的性能。例如,在处理需要连续访问数据的操作时(如字符串处理、图像处理等),使用柔性数组可以减少缓存未命中率并提高访问速度
  • 增强代码可读性:通过将数据和结构体结合在一起,我们可以使代码更加清晰易懂。这有助于维护和理解复杂的程序结构。

2. 应用场景

柔性数组在许多领域都有广泛的应用,包括但不限于以下几个方面:

  • 网络通信在网络通信中,数据包的大小通常是可变的。通过使用柔性数组,我们可以轻松地创建可变大小的数据包结构体来处理不同类型的网络消息
  • 文件处理在处理文件时,文件的内容大小和格式可能是未知的或可变的。柔性数组可以用于创建可变大小的文件头或记录结构体来读取和处理文件中的数据
  • 图像处理在图像处理中,图像的大小和分辨率可能是不同的。通过使用柔性数组,我们可以创建可变大小的图像数据结构来存储和处理不同尺寸的图像
  • 数据库操作在数据库操作中,记录的长度和内容可能是变化的。柔性数组可以用于创建可变大小的记录结构体来存储和操作数据库中的数据

四 注意事项与常见问题

1. 注意事项

  • 确保柔性数组是最后一个成员:如前所述,柔性数组必须是结构体的最后一个成员。否则,编译器将无法确定结构体的大小并进行正确的内存分配。
  • 避免多次分配内存:在使用柔性数组时,应尽量避免对结构体进行多次内存分配。最好是一次性分配足够的内存来容纳所有成员(包括柔性数组部分)。这样可以减少内存碎片和提高性能。
  • 正确处理内存释放:在使用完柔性数组后,应确保正确释放分配的内存以避免内存泄漏。这通常是通过调用free函数来实现的。但是需要注意的是,如果结构体中还包含了其他动态分配的资源(如指针指向的字符串或其他数据结构),则需要分别释放这些资源

2. 常见问题

  • 编译器支持问题:尽管柔性数组是C99标准的一部分,但并不是所有的编译器都完全支持这一特性。因此,在使用之前请检查您的编译器是否支持柔性数组以及相关的语法和功能。
  • 跨平台兼容性:由于不同平台和编译器之间的实现差异可能会导致柔性数组的行为有所不同。因此,在进行跨平台开发时请谨慎使用柔性数组并确保在不同平台上进行测试和验证。
  • 安全性问题:在使用柔性数组时需要注意安全性问题。例如,在将数据复制到柔性数组中时要确保不会超出分配的内存范围导致缓冲区溢出等问题。此外还需要注意防止内存泄漏和其他常见的安全问题。

五 总结与展望

  • 柔性数组是C语言中一个非常有用的特性,它允许开发者创建可变大小的结构体以适应不同的数据需求。通过使用柔性数组,我们可以简化内存管理、提高性能和增强代码可读性。然而,在使用柔性数组时也需要注意一些问题和限制,如确保它是结构体的最后一个成员、避免多次分配内存以及正确处理内存释放等。随着技术的不断发展,未来可能会有更多的优化和改进来提高柔性数组的性能和可靠性。同时我们也期待看到更多创新的应用场景出现以充分利用这一强大的特性。

快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重温设计模式--观察者模式

文章目录 观察者模式&#xff08;Observer Pattern&#xff09;概述观察者模式UML图作用&#xff1a;实现对象间的解耦支持一对多的依赖关系易于维护和扩展 观察者模式的结构抽象主题&#xff08;Subject&#xff09;&#xff1a;具体主题&#xff08;Concrete Subject&#xf…

python使用pip进行库的下载

前言 现如今有太多的python编译软件&#xff0c;其库的下载也是五花八门&#xff0c;但在作者看来&#xff0c;无论是哪种方法都是万变不离其宗&#xff0c;即pip下载。 pip是python的包管理工具&#xff0c;无论你是用的什么python软件&#xff0c;都可以用pip进行库的下载。 …

Apache Solr RCE(CVE-2017-12629)--vulhub

Apache Solr 远程命令执行漏洞&#xff08;CVE-2017-12629&#xff09; Apache Solr 是一个开源的搜索服务器。Solr 使用 Java 语言开发&#xff0c;主要基于 HTTP 和 Apache Lucene 实现。原理大致是文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个…

apisix转发websocket

1、说明 apisix网关对接websocket&#xff0c;参数以及使用可以看官方文档 WebSocket Authentication | Apache APISIX -- Cloud-Native API Gateway 注意事项&#xff1a; &#xff08;1&#xff09;官方文档是websocket要加认证&#xff0c;但自测发现可以不加认证插件 …

【unity】【游戏开发】Unity项目一运行就蓝屏报Watch Dog Timeout

【背景】 由于是蓝屏所以没法截屏&#xff0c;总之今天遇到了一开Unity&#xff0c;过一阵就蓝屏的情况&#xff0c;报Watch Dog Timeout。 【分析】 通过任务管理器查看&#xff0c;发现Unity占用率100%&#xff0c;再观察Unity内部&#xff0c;每次右下角出现一个Global I…

【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用(2024/12/12补充)

最终效果 文章目录 最终效果更好的方式&#xff08;2024/12/12补充&#xff09;前言为什么使用CharacterControllerSimpleMove和Move如何选择&#xff1f;1. SimpleMove2. Move 配置CharacterController参数控制相机移动跳跃方式一方式二 下蹲处理下坡抖动问题实现奔跑和不同移…

sentinel学习笔记6-限流降级(上)

本文属于sentinel学习笔记系列。网上看到吴就业老师的专栏&#xff0c;写的好值得推荐&#xff0c;我整理的有所删减&#xff0c;推荐看原文。 https://blog.csdn.net/baidu_28523317/category_10400605.html sentinel 实现限流降级、熔断降级、黑白名单限流降级、系统自适应…

使用 UniApp 在微信小程序中实现 SSE 流式响应

概述 服务端发送事件&#xff08;Server-Sent Events, SSE&#xff09;是一种允许服务器向客户端推送实时更新的技术。SSE 提供了一种单向的通信通道&#xff0c;服务器可以持续地向客户端发送数据&#xff0c;而不需要客户端频繁发起请求。这对于需要实时更新的应用场景非常有…

监控易在汽车制造行业信息化运维中的应用案例

引言 随着汽车制造行业的数字化转型不断深入&#xff0c;信息化类IT软硬件设备的运行状态监控、故障告警、报表报告以及网络运行状态监控等成为了企业运维管理的关键环节。监控易作为一款全面、高效的信息化运维管理工具&#xff0c;在汽车制造行业中发挥着重要作用。本文将结合…

Unity DOTS中的share component

Unity DOTS中的share component 内存管理创建流程修改流程销毁流程Reference share component是DOTS中一类比较特殊的component&#xff0c;顾名思义&#xff0c;它是全局共享的component&#xff0c;所有具有相同component值的entity&#xff0c;共享一个component&#xff0c…

外连接转AntiJoin的应用场景与限制条件 | OceanBase SQL 查询改写系列

在《SQL 改写系列&#xff1a;外连接转内连接的常见场景与错误》一文中&#xff0c;我们了解到谓词条件可以过滤掉连接结果中的 null 情形的&#xff0c;将外连接转化为内连接的做法是可行的&#xff0c;正如图1中路径(a)所示。此时&#xff0c;敏锐的你或许会进一步思考&#…

Facebook的去中心化探索:社交平台的新型发展趋势

随着数字化进程的加速&#xff0c;社交平台的架构正在经历一场深刻的变革。从最初的集中的社交网络到如今去中心化的构想&#xff0c;社交平台正在朝着更加透明、开放和用户主导的方向发展。作为全球最大的社交平台之一&#xff0c;Facebook&#xff08;现Meta&#xff09;也在…

Facebook 与数字社交的未来走向

随着数字技术的飞速发展&#xff0c;社交平台的角色和形式也在不断演变。作为全球最大社交平台之一&#xff0c;Facebook&#xff08;现Meta&#xff09;在推动数字社交的进程中扮演了至关重要的角色。然而&#xff0c;随着互联网的去中心化趋势和新技术的崛起&#xff0c;Face…

LeetCode:222.完全二叉树节点的数量

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;222.完全二叉树节点的数量 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二…

python+opencv+棋盘格实现相机标定及相对位姿估计

pythonopencv棋盘格实现相机标定及相对位姿估计 引言1&#xff0c;使用相机采集含棋盘格图像14张2&#xff0c;进行相机标定&#xff08;1&#xff09;测试软件1标定结果&#xff08;内参及畸变系数&#xff09;&#xff08;2&#xff09;测试软件2标定结果&#xff08;内参及畸…

Vue(四)

1.Vuex 1.1 Vuex是什么 Vuex 是一个插件&#xff0c;可以帮我们管理 Vue 通用的数据。例如&#xff1a;购物车数据、个人信息数据。 1.2 vuex的使用 1.安装 vuex 安装 vuex 与 vue-router 类似&#xff0c;vuex 是一个独立存在的插件&#xff0c;如果脚手架初始化没有选 v…

ShardingSphere-Proxy 连接实战:从 Golang 原生 SQL 到 GORM 的应用

在这篇文章《ShardingSphereProxy:快速入门》中&#xff0c;我们介绍了如何通过 Navicat 连接 ShardingSphere-Proxy。 实际上&#xff0c;ShardingSphere-Proxy 兼容标准的 SQL 和原生数据库协议&#xff0c;因此你可以使用任何 MySQL 客户端与其进行连接&#xff0c;包括 Go…

芝法酱学习笔记(2.2)——sql性能优化2

一、前言 在上一节中&#xff0c;我们使用实验的方式&#xff0c;验证了销售单报表的一些sql性能优化的猜想。但实验结果出乎我们的意料&#xff0c;首先是时间查询使用char和datetime相比&#xff0c;char可能更快&#xff0c;使用bigint&#xff08;转为秒&#xff09;和cha…

IntelliJ IDEA 快捷键大全:提升开发效率的利器

目录 一、基础快捷键 1. 文件操作快捷键 2. 编辑&#xff08;Editing&#xff09; 2.1 代码补全与导航 2.2 代码编辑 2.3 代码折叠与展开 3. 查找与替换 4. 调试 5. 版本控制 高级快捷键 重构快捷键&#xff1a;让代码更加优雅 导航快捷键&#xff1a;快速定位代码 …

基于Qlearning强化学习的机器人路线规划matlab仿真

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下&#xff08;完整代码运行后无水印&#xff09;&#xff1a; 训练过程 测试结果 仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论…