常见的动态内存的错误 和 柔性数组

简介: ✅<1>主页:C语言的前男友📃<2>知识讲解:常见的动态内存的错误,以及柔性数组(C99)🔥<3>创作者:C语言的前男友☂️<4>开发环境:Visual Studio 2022💬<5>前言:之前我们说过动态内存管理的知识,今天我们来见识一下,动态内存管理常见的错误。帮助大家对动态内存管理有更深入的理解。

🍍一.对NULL指针的解引用操作

代码:

void test()
{
    int *p = (int *)malloc(INT_MAX/4);
    *p = 20;//如果p的值是NULL,就会有问题
    free(p);
}

如果 malloc 开辟空间失败就会形成对 NULL 指针的解引用。


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

代码:

void test()
{
    int i = 0;
    int* p = (int*)malloc(10 * sizeof(int));
    if (NULL == p)
    {
        exit(EXIT_FAILURE);
    }
    for (i = 0; i <= 10; i++)
    {
        *(p + i) = i;//当i是10的时候越界访问
    }
    free(p);
}

malloc开辟的空间与数组一样,只不过是在堆上开辟的空间,但是也是不能越界访问的。例如上述代码我们只开辟了,是个 int 的空间所以我们在访问时就只能访问是个整形。


🏡三.对非动态开辟内存使用free释放

void test()
{
    int a = 10;
    int* p = &a;
    free(p);//ok?
}



🍐 四.使用free释放一块动态开辟内存的一部分

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



这个时候在对空间进行释放就会出错。



🍉  五.对同一块动态内存多次释放

代码:

void test()
{
  int* p = (int*)malloc(100);
  free(p);
  free(p);//重复释放
}



🍓六.动态开辟内存忘记释放(内存泄漏)

代码:

void test()
{
  int* p = (int*)malloc(100);
  if (NULL != p)
  {
    *p = 20;
  }
}
int main()
{
  test();
  while (1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏。

切记:
动态开辟的空间一定要释放,并且正确释放,虽然代码运行起来不会有什么错误,但是内存会被一点一点吃掉,直到内存吃完了,程序停止。


🍉例题一.

void GetMemory(char* p)
{
  p = (char*)malloc(100);
}
int main()
{
  char *str=NULL;
  GetMemory(str);
  strcpy(str, "hello world");
  printf(str);
}

这里的大概思路是,定义一个指针str ,调用GetMemory函数让指针指向 malloc 开辟的空间。再将字符串拷贝到 str 所指向的空间内,最后打印字符串。

但是事实却是:



为什么会出现这样的错误呢?

看似我们让指针指向了 malloc 开辟的空间,但是事实上并没有改变 str 的值,因为形参 p,和实参 str,他俩只是值相同而已,对形参 p 的改变,不会影响实参 str 。所以 str 还是空的。所以后面在调用 strcpy 的时候就会出现对NULL指针的使用错误。程序就会崩掉。

这是这个代码的最大的问题,还有一些小问题,这里的 malloc 的空间并没有释放。


那这个代码改怎么改呢?我们是希望malloc开辟的空间能够交给 str 来指引。意思就是能过改变 str 的指向,使得 str 原本指向NULL,变成malloc开辟的空间。这里就想到了传址调用。所以我们要用str 的地址作为函数参数。形参用二级指针接受。

void GetMemory(char** p)
{
  *p = (char*)malloc(100);
}
int main()
{
  char* str = NULL;
  GetMemory(&str);
  strcpy(str, "hello world");
  printf(str);
  free(str);
  str=NULL;
    return 0;
}



还有一些同学应该早早就发现了,这里的打印字符串是不是有错误啊?我们平时不是这样的写的呀。

printf(str);

这里的打印字符串是没有错误的。



🍚 例二:

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

大致思路是,创建一个指针 str ,调用函数 GetMemory,函数内部创建一个字符串数组,并返回 数组名。用指针 str 经行接收,并打印。


一切都是那么的合情合理,但是结果总是那么的不尽人意。


f805833a64e84f0c8cec3b7f29c45987.png


简单分析一下,我们知道函数都是在栈上面开辟函数栈帧的,函数内部创建的变量都是局部变量,局部变量随着函数栈帧的销毁而销毁。当函数返回以后就意味着函数的栈帧也就销毁了。里面的局部变量也就不再是有效的局部变量了。就算返回了那块空间的地址,但是那块空间的内容已经无效了。所以才出现这种打印出的乱码。这是一种常见的错误叫做:返回栈空间地址。


🍒例三:

int main()
{
  char* str = (char*)malloc(100);
  strcpy(str, "hello");
  free(str);
  if (str != NULL)
  {
    strcpy(str, "world");
    printf(str);
  }
  return 0;
}


8c7fd17e90cd4ee7899736c4fc89f003.png


这里大家看到代码成功跑起来了,也得到了想要的结果。这总该没问题了吧。


其实这个代码还是有问题的,只不过编译器没有发现而已。


问题在于我们申请的空间,交给 str ,但是已经释放了,free以后就相当于是把malloc申请的空间还给 OS 了,但是 str 还是指向那块空间的。所以 str 仍不为空。 但是这里再将 "world"拷贝给 str,就是有问题的了。


🍊柔性数组:

🍉  (1)定义:

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。

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

实现:

typedef struct st_type
{
  int i;
  int a[0];//柔性数组成员
}type_a;

有些编译器会报错无法编译可以改成:

typedef struct st_type
{
  int i;
  int a[];//柔性数组成员
}type_a;

🍉  (2)柔性数组的特点:

1.结构中的柔性数组成员前面必须至少一个其他成员。


2.sizeof 返回的这种结构大小不包括柔性数组的内存。


28f353d2250847d08291d13104048ff6.png


3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小  

🍉  (3)柔性数组的使用

typedef struct st_type
{
  int i;
  int a[0];//柔性数组成员
}type_a;
int main()
{
  //创建结构体,sizeof(int)*5为柔性数组开辟五个整形空间。
  type_a* stra = (int*)malloc(sizeof(type_a) + sizeof(int) * 5);
  stra->i = 100;
  for (int i = 0; i < 5; i++)
  {
    stra->a[i] = i;
  }
  for (int i = 0; i < 5; i++)
  {
    printf("%d ", stra->a[i]);
  }
  printf("stra->i=%d", stra->i);
  return 0;
}

 

🍭最后:

不是一番寒彻骨,怎得梅花扑鼻香。


4.jpg

相关文章
|
7月前
|
编译器
动态内存管理与柔性数组 1
动态内存管理与柔性数组
27 0
|
7月前
|
程序员 C语言 C++
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(下)
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(下)
27 0
|
7月前
|
程序员 编译器 C语言
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(上)
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(上)
34 0
|
7月前
|
程序员 编译器 C语言
动态内存函数,内存开辟,柔性数组(超详细)
动态内存函数,内存开辟,柔性数组(超详细)
41 0
|
7月前
|
编译器 程序员 测试技术
详解动态内存管理【malloc/calloc/realloc/free函数/柔性数组】【C语言/进阶/数据结构基础】
详解动态内存管理【malloc/calloc/realloc/free函数/柔性数组】【C语言/进阶/数据结构基础】
174 0
|
7月前
|
编译器 程序员 C语言
【C语言】动态内存管理(malloc,free,calloc,realloc,柔性数组)
【C语言】动态内存管理(malloc,free,calloc,realloc,柔性数组)
|
6天前
|
C语言
C语言——动态内存管理(malloc, calloc, realloc, free, 柔性数组详解)
C语言——动态内存管理(malloc, calloc, realloc, free, 柔性数组详解)
|
6天前
|
存储 编译器 C语言
learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结
learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结
|
9月前
|
程序员 C语言 C++
c语言学习第三十二课---内存开辟位置与柔性数组
c语言学习第三十二课---内存开辟位置与柔性数组
61 0
|
6天前
|
存储 程序员 编译器
动态内存管理+柔性数组+经典笔试题
动态内存管理+柔性数组+经典笔试题
47 0