有关动态内存分配的错误使用和题目(详解)

简介: 动态内存分配(有关动态内存分配的题目和常见的错误)一、首先我们先看一下(常见的动态内存的错误)1.直接对NULL的解引用操作2.对动态开辟的内存的越界访问3.对非动态开辟内存使用free释放4.使用free释放一块动态内存开辟内存的一部分5.对同一块动态内存的多次释放6.动态开辟内存忘记释放(导致内存的泄露)二、接下来我们介绍一下几个经典的有关动态内存开辟的题目(详解坑人处)第一题:第二题:第三题:第四题:三、所以我们这边再用一幅图来理解一下什么是内存空间四、总结:动态内存的使用真的非常的重要,所以各位小伙伴们一定要熟练掌握哦!

动态内存分配(有关动态内存分配的题目和常见的错误)

一、首先我们先看一下(常见的动态内存的错误)

1.直接对NULL的解引用操作

代码如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main()
{
  int* p = (int*)malloc(40);
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *(p + i) = i;
  }
  for (i = 0; i < 10; i++)
  {
    printf("%d ", *(p + i));
  }
  free(p);
  p = NULL;
  return 0;
}

(1.)这个就是一个错误写法,因为此时没有进行判断malloc是否开辟内存成功,假如失败,此时这个指针就是一个空指针,所以下面对空指针进行解引用,就是一个非法访问的操作,所以这是一个错误的写法

(2.)所以我们在进行开辟动态空间的时候就一定要对其进行判断,只有这样才可以避免这个问题

(3.)正确写法如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main()
{
  int* p = (int*)malloc(40);
  int i = 0;
  if (p != NULL)
  {
    for (i = 0; i < 10; i++)
    {
      *(p + i) = i;
    }
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));
    }
  }
  free(p);
  p = NULL;
  return 0;
}

2.对动态开辟的内存的越界访问

(1.)代码如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main()
{
  int* p = (int*)malloc(5 * sizeof(int));
  if (p == NULL)
  {
    return 0;
  }
  else
  {
    int i = 0;
    for (i = 0; i < 10; i++)//越界访问(因为我只开辟了5个整形的空间,而现在却使用了10个整形的空间,所以就会造成非法访问空间)
    {
      *(p + i) = i;
    }
  }
  free(p);
  p = NULL;
  return 0;
}

(1.)越界访问(因为我只开辟了5个整形的空间,而现在在循环中却循环了10个整形的空间,所以就会造成非法访问空间)

(2.)所以在使用动态内存空间的时候就一定要注意我开辟的内存到底有多大

(3.)正确写法如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main()
{
  int* p = (int*)malloc(5 * sizeof(int));
  if (p == NULL)
  {
    return 0;
  }
  else
  {
    int i = 0;
    for (i = 0; i < 5; i++)
    {
      *(p + i) = i;
    }
  }
  free(p);
  p = NULL;
  return 0;
}

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

(1.)这个就非常好理解(就是free的作用只能是释放开辟的内存)

(2.)错误代码如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
void test(int*p)
{
  *p = 20;
  free(p);
  p = NULL;
}
int main()
{
  int a = 10;
  test(&a);
  printf("%d", a);
  return 0;
}

(3.)因为此时这个a变量创建是在栈区上进行的,而我的free释放的动态内存是在堆区上进行,所以我不能用free去释放栈区的空间,我只能释放堆区的空间,所以这个写法也是错误的

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

(1.)先看代码:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p != NULL)
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      *p++ = i;//小知识点后置++,先使用后加加
      //*(p+i)=i;
    }
  }
  return 0;
}

(2.)主要的错误就是在*p++ = i;虽然此时可以和 (p+i)=i;起到相同的作用,但是意思却完全不一样 了,因为如果写成p++ = i;此时就会使我的指针的位置一直向后移动(指针向后移动后才可以把我的 i 的数据放进去 ),所以就导致了p指针指向的位置发生了改变(从本来指向首元素的位置变到了指向存放数据9的那个地址处),所以此时的p指针指向的地址就再也不是我原来向堆区申请的那个空间了,所以如果此时进行free(释放内存),就会导致释放的内存不是我原来开辟的完整的内存,只是一部分而已,所以我释放内存时,就会释放一些未知的内存,所以又会导致内存的非法访问

(3.)所以当我们在使用动态内存,就一定不能去改变此时这个指针指向的地址(使用时就应该对其进行解引用后再去使用)

(4.)代码修改如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p != NULL)
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      *(p+i)=i;//对其进行解引用使用(用下标完成任务)
    }
  }
  return 0;
}

5.对同一块动态内存的多次释放

(1.)这个好理解,代码如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p != NULL)
  {
    //……
    free(p);
    free(p);
  }
  return 0;
}

2.)就是对同一个动态内存进行多次释放,这个也是错误的

(3.)避免的好办法就是谁开辟谁释放和每一次进行动态内存的释放(就在后面加上一句p = NULL)

这样也可以防止多次释放(因为free空指针并不影响)

6.动态开辟内存忘记释放(导致内存的泄露)

(1.)代码如下:】

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<windows.h>
int main()
{
     while(1)
     {
        malloc(10);
        Sleep(1000);//睡眠函数,睡眠1000毫秒的意思,就是1秒(头文件 #include<windows.h>)
     }
     return 0;
}

(2.)这样一直死循环的进行动态开辟内存,然后没有进行内存的释放,就会导致我的内存一直减少,直到没有内存可以使用

(3.)所以只要进行了动态开辟内存就一定要进行free (否则内存泄露是非常吓人的)

(4.)修改如下:

#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<windows.h>
int main()
{
     while(1)
     {
        int* p = (int*)malloc(10);
        Sleep(1000);
     }
     free(p)
     p=NULL;
     return 0;
}

二、接下来我们介绍一下几个经典的有关动态内存开辟的题目(详解坑人处)

第一题:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
void GetMemory(char* p)
{
  p = (char*)malloc(40);
}
void test(void)
{
  char* str = NULL;
  GetMemory(str);
  strcpy(str, "hello world");
  printf(str);
}
int main()
{
  test();
  return 0;
}

讲解:

(1.)首先最明显的错误是如上面进行错误介绍所说,这边没有进行动态开辟内存的释放(所以有内存泄露的问题)

(2.)第二个错误就是因为:当我在一个函数中开辟空间时并且我传参的方式为传值,此时函数中的参数只是一份临时拷贝,使其出了这个函数的范围时,这个参数就失效了,所以我在GetMemory这个函数中开辟的40个字节的空间并不会改变我函数外部的任何参数,所以此时的内存开辟是一个失败的开辟,所以此时我的str指针并没有空间,此时还是一个空指针,所以此时我把"hello world"拷贝到一个空指正中,就导致非法访问内存造成了程序的失败

(3.)举一个例子:好比此时我开辟的40个字节的空间就是一个卧底,而我的p指针就是卧底的上司,只有上司知道卧底的身份,然而后来上司死了(也就是出了函数的范围),此时就没有人知道卧底的身份了,所以就会出问题

(4.)所以在函数中独立开辟空间时,一定要注意函数的传参形式(不敢用传值调用)

(5.)接下来顺便让我们理解一下printf(str);这个写法的意思

代码如下:

int main()
{
  char* str = "abcdef";
  printf("%s\n",str);
  printf("abcdef\n");
  printf(str);//意思此时我把abcdef的地址放在了str这个指针变量中,所以此时str就是abcdef的地址,所以直接打印就行
  return 0;
}

(6.)这3中写法的意思是一模一样的

输出结果如图:

0.png


(7.)所以有时要关注好指针的用法

(8.)第一种修改方式如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
void GetMemory(char** p)//注意这个二级指针
{
  *p = (char*)malloc(40);//注意这个解引用
}
void test(void)
{
  char* str = NULL;
  GetMemory(&str);//注意这个传参
  strcpy(str, "Hello World");
  printf(str);//注意这个打印
  free(str);//注意释放空间
  str = NULL;
}
int main()
{
  test();
  return 0;
}

(9.)修改了两处:

1.加上了free释放了内存

2.用传址的方式进行传参(但是要注意str原来的类型就是char*,所以接收时应该要用一个char**的二级指针来接收,并且要对p进行解引用,获得它的地址,这样才可以把我动态开辟的内存给放到这个地址处,不敢就写一个p不进行解引用,这样写仅代表的是一个指针,指针是不能接受空间的,所以一定要进行解引用)

(10.)第二种修改方式:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
char* GetMemory(char* p)
{
  p = (char*)malloc(40);
    return p;//返回一个p给下面的代码,但是注意p的类型是char*,所以我的返回类型也要写char*
}
void test(void)
{
  char* str = NULL;
  str = GetMemory(str);//并且这边刚好用str来接收这个返回值(意思就是把开辟的空间赋给我的空指针str),所以此时str就是一个指向了40个字节空间的指针了
  strcpy(str, "Hello World");
  printf(str);//这个鬼的理解写在下面
  free(str);
  str = NULL;
}
int main()
{
  test();
  return 0;
}

(11.)这种修改方式就是出函数时可以把开辟的内存的地址返回给我,此时我再用我的str空指针去接收

(12.)所以方法千千万,就看你牛不牛啦!

第二题:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
char* GetMemory(void)
{
  char p[] = "Hello World";
  return p;
}
void test(void)
{
  char* str = NULL;
  str = GetMemory();
  printf(str);
}
int main()
{
  test();
  return 0;
}

(1.)这题的关键就是你怎样去理解 Get Memory这个函数,和char p[ ] = “Hello World”;这个字符串数组的创建

(2.)首先这边肯定是没有内存泄露的(因为我根本就没有向堆区申请空间),没有动态开辟内存

(3.)char p[ ] = “Hello World”;首先可以看出这是一个局部数组变量的创建,这个创建根据以前的知识可以知道是在栈区上创建的,所以我在函数内部返回一个栈区上的地址(只要一出函数),这个地址中的内容就会销毁,所以此时这个栈区上的空间里面并没有放我想要的数据,返会的地址只是一个内存中随机的空间,并无实际作用,所以此时我用str这个空指针去接收我的这个地址,就是一个典型的(非法访问未知空间的操作),所以此时打印str时,就没有人会知道这个地址中到底存放的是什么东西

(4.)打印结果如下:


1.png

(5.)完美佐证我们上述的理解

(6.)所以如果我们想要进行改正,我们就要引入一个(static)的概念,把这个变量的生命周期延长,使其出了函数外部也不会被销毁(不会被销毁的原因在于,此时我是把这个变量放在了我内存中的静态区,就不是栈区了,所以就不存在返回栈区空间的情况,所以此时不会销毁,可以正常使用)

(7.)改正后的代码如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
char* GetMemory(void)
{
  static char p[] = "Hello World";
  return p;
}
void test(void)
{
  char* str = NULL;
  str = GetMemory();
  printf(str);
}
int main()
{
  test();
  return 0;
}

(8.)加上这个static之后这个代码就是正确的了(生命周期变长)

(9.)所以只要我不返回栈区上的空间就行(所以可以返回静态区和堆区上的空间),前提是堆区上的空间没有被free(释放)

第三题:

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

(1.)这个的问题就只有一个就是没有进行内存释放(导致内存泄露的问题)

(2.)改正如下:

void GetMemory(char** p, int num)
{
  *p = (char*)malloc(num);
}
void test(void)
{
  char* str = NULL;
  GetMemory(&str, 100);
  strcpy(str, "hello");
  printf(str);
  free(str);//加上这个释放就行
  str=NULL;
}
int main()
{
  test();
  return 0;
}

第四题:

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

(1.)首先这个代码最后是输出world

(2.)原因就是我释放我创建的动态内存是,释放后str此时并不是一个NULL指针,而是指向一个随机位置的指针,所以 if 语句的判断成功,所以进去打印world

(3.)然后问题依然是会非法访问内存(因为此时我已经把我的动态开辟的内存给释放掉的,所以此时这个str指针指向的那个地址,还是一个内存中的位置内存,所以此时你想把world放到str指针指向的地址中,此时你就造成了非法访问内存中的未知位置的空间)

(4.)改正代码如下:

void test(void)
{
  char* str = (char*)malloc(100);
  strcpy(str, "hello");
  free(str);
  str=NULL;//这边把str赋成空指针就行,这样此时它就不会再指向原来那个空间的地址,然后对str操作时,就不会再影响原来那个空间了
  if (str != NULL)
  {
    strcpy(str, "world");
    printf(str);
  }
}
int main()
{
  test();
  return 0;
}

(5.)str=NULL; 这边把str赋成空指针就行,这样此时它就不会再指向原来那个空间的地址,然后对str操作时,就不会再影响原来那个空间了

三、所以我们这边再用一幅图来理解一下什么是内存空间

2.jpeg


1.这幅图就能很好的说明各种数据类型在内存中是如何储存的

四、总结:动态内存的使用真的非常的重要,所以各位小伙伴们一定要熟练掌握哦!

相关文章
|
7月前
动态内存管理题目讲解
动态内存管理题目讲解
|
22天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
173 1
|
11天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
20天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
21天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
19 3
|
22天前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
44 1
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
74 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
2月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
56 2