【C语言进阶(九)】常见内存错误以及柔性数组

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C语言学习分享

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多C语言知识

🔝🔝



常见内存错误

  • [1. 前言](#1. 前言 "#1__14")
  • [2. 对NULL指针解引用操作](#2. 对NULL指针解引用操作 "#2_NULL_32")
  • [3. 对动态开辟的空间越界访问](#3. 对动态开辟的空间越界访问 "#3___72")
  • [4. free没有将空间完全释放](#4. free没有将空间完全释放 "#4_free_99")
  • [5. 多次释放或忘记释放](#5. 多次释放或忘记释放 "#5__121")
  • [6. 经典笔试题目](#6. 经典笔试题目 "#6__139")
    • [6.1 题目一](#6.1 题目一 "#61__141")
    • [6.2 题目二](#6.2 题目二 "#62___173")
  • [7. 柔性数组](#7. 柔性数组 "#7__213")
    • [7.1 柔性数组的特点](#7.1 柔性数组的特点 "#71__230")
    • [7.2 柔性数组的使用](#7.2 柔性数组的使用 "#72__260")
    • [7.3 柔性数组的优势](#7.3 柔性数组的优势 "#73__286")
  • [8. 总结以及拓展](#8. 总结以及拓展 "#8__329")

1. 前言

本章重点:

本节着重讲解动态内存中的常见错误
并且分享几个经典的笔试题
并且介绍一个新概念: 柔性数组

动态开辟的内存在堆区,局部变量在栈区
它们的作用域什么时候销毁?
它们之间能不能相互关联起来使用?
包括一些错误的用法在面试中是常客!


2. 对NULL指针解引用操作

请看下面的代码:

c 复制代码
void test()
{
 	 int *p = (int *)malloc(40);
	 *p = 20;//如果p的值是NULL,就会有问题
	 free(p);
}

解释:

malloc函数开辟空间失败会返回NULL
而每次使用完malloc后就应该判空
否则使用这个指针时不知道它是否为空

拓展:

现在的编译器很智能,电脑本身性能也强

malloc一般都不会失败

即使开辟五万个字节的空间

空间肯定也是会开辟成功的!
但是为了养成良好的习惯,应该记得判空


早在12年的时候,就有大佬回答:
堆区空间大约2GB
2GB=2×1024×1024×1024 个字节
暂且别说五万个字节的空间
就算是5亿个字节也完全能开辟出来


3. 对动态开辟的空间越界访问

请看以下代码:

c 复制代码
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
	 exit(-1);
}
 for(i=0; i<=10; i++)
{
	 *(p+i) = i;//当i是10的时候越界访问
}
free(p);

解释:

此处的越界访问和数组的越界访问类似
在开辟的空间外面的空间是未知的值
这里不做过多讲解


4. free没有将空间完全释放

请看以下代码:

c 复制代码
void test()
{
  int *p = (int *)malloc(100);
  p++;
  free(p);//p不再指向动态内存的起始位置
}

解释:

这段代码可以这样理解:


5. 多次释放或忘记释放

请看以下代码:

c 复制代码
int *p = (int *)malloc(100);
int *pp = (int *)malloc(100);//pp没有释放
 free(p);
 free(p);//重复释放

注意:
这里比较简单,但是值得注意的是
由于写代码时写着写着容易忘记释放空间
所以我建议在写完malloc的下面一条语句
直接写上free,再在它们中间写其他代码


6. 经典笔试题目

6.1 题目一

c 复制代码
void GetMemory(char *p)
{
  p = (char *)malloc(100);
}
void Test(void)
{
  char *str = NULL;
  GetMemory(str);
  strcpy(str, "hello world");
  printf(str);
}

这段代码的问题是什么?

解释:

首先,Get函数的参数是char
str的类型也是char
,所以是传值传参

而形参p和实参str没有必然联系

改变形参p不会对str造成影响

所以应该传str的地址,用二级指针接受**
其次,这份动态开辟的空间没有释放
并且此时str还是NULL
使用strcpy相当于对NULL解引用会报错


6.2 题目二

c 复制代码
char *GetMemory(void)
{
  char p[] = "hello world";
  return p;
}
void Test(void)
{
  char *str = NULL;
  str = GetMemory();
  printf(str);
}

这段代码有什么问题?

解释:

首先,p指向的空间是栈区开辟的
出Get函数后,这份空间就返给了系统
使用已经还给系统的空间是不对的!

其次,这段代码不会打印hello world

函数的栈帧的建立和销毁的过程

大致可以这样理解:

拓展:

函数栈帧的创建与销毁过程
可以参考这篇博客:

函数栈帧的创建与销毁


7. 柔性数组

结构体中的最后一个元素
允许是未知大小的数组
这就叫做『柔性数组』成员。
柔性数组的元素个数是可变的

比如:

c 复制代码
struct NEO
{
  int i;
  int a[0];//柔性数组成员
  //int a[];或者使用这种写法
}

7.1 柔性数组的特点

基本特点:

  • 柔性数组前面至少有一个成员
  • 柔性数组必须是最后一个成员
  • 计算结构体大小时
    柔性数组不计算在内
  • 为拥有柔性数组的结构体开辟空间时
    除了结构体大小外还要加上数组大小

例如:

c 复制代码
struct NEO
{
 int i;
 int a[0];//柔性数组成员
}
printf("%d\n", sizeof(struct NEO));

打印4

这里只会计算成员i的大小

而柔性数组的大小不算在内


7.2 柔性数组的使用

先定义一个柔性数组:

c 复制代码
struct NEO
{
  int i;
  char ch;
  int a[0];//柔性数组成员
}

假设想让数组a有10个整型的空间

可以这样开辟空间:

c 复制代码
struct NEO* p = (struct NEO*)malloc(sizeof(struct NEO)+10*sizeof(int));

这段代码可以这样理解:


7.3 柔性数组的优势

在定义结构体时,假设使用正常的指针p

c 复制代码
struct NEO
{
  int i;
  int* p;
  char ch;
}

struct NEO* n;

然后为指针p动态开辟一份空间

首先要为结构体变量开辟空间

c 复制代码
n = (struct NEO*)malloc(sizeof(struct NEO))
n->p = (int*)malloc(sizeof(int)*10);

然而使用这个变量后,需要free掉空间
malloc了两次空间,所以需要释放两次

先释放谁? 当然是内部是指针p!
如果先把结构体变量n释放了
那么就找不到内部的指针p了

发现不使用柔性数组的话,很麻烦
并且很容易将释放顺序搞反

总结柔性数组的优势:

  1. 好处一:方便内存释放
  2. 好处二:有利于访问速度

使用柔性数组时,数组和其他成员的内存
是连续的而使用指针时,内存不连续


8. 总结以及拓展

使用动态开辟空间解决问题固然方便
但是稍不注意就会出现内存问题
这是一把双刃剑,使用应谨慎

拓展:柔性数组的用处

可以参考这篇文章:

开发使用方式之柔性数组

柔性数组拓展阅读:

结构体中的成员数组和指针


🔎 下期预告:文件操作 🔍

相关推荐
寒水馨2 分钟前
Linux下载、安装 Codex CLI(附安装包codex-x86_64-unknown-linux-musl.tar.gz)
linux·openai·终端·ai编程·codex·智能体·codex cli
冰封之寂9 小时前
Docker 资源管理终极指南:CPU、内存与 IO 的精细化控制
linux·运维·服务器·docker·云计算
xiaoye-duck9 小时前
《Linux系统编程》Linux 系统多线程(五):<线程同步与互斥>线程互斥(上):从条件变量到生产者消费者模型详解
linux·线程
一叶龙洲11 小时前
向日葵远程Ubuntu,支持隐私屏
linux·运维·ubuntu
酷可达拉斯11 小时前
Linux操作系统-shell编程之Grep与正则表达式
linux·运维·服务器·正则表达式·centos
寒水馨12 小时前
Linux下载、安装llama.cpp-b10068(附安装包llama-b10068-bin-ubuntu-vulkan-x64.tar.gz)
linux·ubuntu·llm·llama·本地部署·llama.cpp·推理引擎
我星期八休息14 小时前
网络编程—UDP与TCP
linux·运维·网络·数据库·网络协议·tcp/ip·udp
ziguo112215 小时前
C/C++ 错误处理全解:从 errno 到 C++ 异常
linux·c语言·c++·windows·visual studio
三84416 小时前
使用Samba/NFS实现文件共享/自动挂载共享目录/autofs自动挂载服务
linux·服务器·网络
高铭杰16 小时前
万物皆可KV(2)SurrealDB 存储布局分析
linux·服务器