【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. 总结以及拓展

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

拓展:柔性数组的用处

可以参考这篇文章:

开发使用方式之柔性数组

柔性数组拓展阅读:

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


🔎 下期预告:文件操作 🔍

相关推荐
2035去旅行4 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
陈希1235 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡6 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.9 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道10 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.13 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落14 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨14 小时前
WSL2的安装与使用
linux·wsl
微小的鱼14 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng15 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器