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

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

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


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


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


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

  🔝🔝


1. 前言

本章重点:

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

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


2. 对NULL指针解引用操作

请看下面的代码:

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. 对动态开辟的空间越界访问

请看以下代码:

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没有将空间完全释放

请看以下代码:

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

解释:

这段代码可以这样理解:


5. 多次释放或忘记释放

请看以下代码:

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

注意:

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


6. 经典笔试题目

6.1 题目一

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 题目二

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

这段代码有什么问题?

解释:

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

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

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

大致可以这样理解:

拓展:

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

函数栈帧的创建与销毁


7. 柔性数组

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

比如:

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

7.1 柔性数组的特点

基本特点:

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

例如:

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

打印4

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

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


7.2 柔性数组的使用

先定义一个柔性数组:

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

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

可以这样开辟空间:

struct NEO* p = (struct NEO*)malloc(sizeof(struct NEO)+10*sizeof(int));

这段代码可以这样理解:


7.3 柔性数组的优势

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

struct NEO
{
  int i;
  int* p;
  char ch;
}
struct NEO* n;

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

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

n = (struct NEO*)malloc(sizeof(struct NEO))
n->p = (int*)malloc(sizeof(int)*10);

然而使用这个变量后,需要free掉空间

malloc了两次空间,所以需要释放两次

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

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

总结柔性数组的优势:

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

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


8. 总结以及拓展

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

拓展:柔性数组的用处

可以参考这篇文章:

开发使用方式之柔性数组

柔性数组拓展阅读:

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


🔎 下期预告:文件操作 🔍



相关文章
|
3月前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
78 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
3月前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
126 6
|
4月前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
94 6
|
4月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
317 13
|
4月前
|
大数据 C语言
C 语言动态内存分配 —— 灵活掌控内存资源
C语言动态内存分配使程序在运行时灵活管理内存资源,通过malloc、calloc、realloc和free等函数实现内存的申请与释放,提高内存使用效率,适应不同应用场景需求。
|
4月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
4月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
102 1
|
4月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
4月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
706 1
|
10天前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
18 3