动态内存分配(2)——经典例题的讲解

简介: 动态内存分配(2)——经典例题的讲解

前言:

       在前面我们已经学习动态分配内存,今天我们就来做一做它的几道经典例题,加深巩固我们所学的知识。

知识复习:动态内存管理(1)_从前慢,现在也慢的博客-CSDN博客

题目1:

       下面代码存在什么问题,请你指出问题并修改。

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

分析:

       1、程序从main开始,运行之后调用Test函数;

       2、进入Test函数,①先创建了一个str的字符指针,并赋初值为NULL;②调用GetMemory函数,以值传递的方式将str传给GetMemory(值传递,形参只是实参的一份临时拷贝,形参的改变不影响实参);

       3、进入GetMemory函数,①p的初始值为NULL(值传递);②malloc在堆区申请开辟100个字节的连续空间,并将malloc的返回值赋值给p(①忘记判断malloc是否开辟成功;②没有自己free释放申请的动态内存,如果程序一直运行造成内存泄漏);因为函数类型是void,所以代码执行完,直接返回到test函数中的GetMemory调用;

       4、GetMemory调用完继续向下执行,调用strcpy函数,但是目标空间的实参为NULL(值传递形参不影响实参),造成了strcpy形参的非法访问,所以程序出错,不再向下执行。

图示:

问题归纳:

问题1:

      GetMemory是传值调用,str传给p的时候,p是str的临时拷贝,有自己独立的空间,当GetMemory函数内部申请完空间后,申请空间的起始地址放在p中,str依然是NULL。当GetMemory函数返回之后。执行strcpy函数拷贝时,strcpy的目标空间形参为NULL,造成非法访问内存。

解决方案:

       ①传址调用(形参影响实参);②通过函数返回值得到。

问题2:

       GetMemory函数内部只动态申请了内存,在Test函数中使用,①但是并没有将malloc的返回值传给Test,②在使用动态内存前没有判断开辟是否成功,③使用完并没有释放空间,如果程序一直运行,会内存泄漏。

解决方案:

       ①使用动态开辟的内存前,判断是否开辟成功;②使用完之后记得free释放。


代码修改1: 通过传址调用,可以间接的得到malloc开辟空间的起始地址,使用前判断是否开辟成功,使用完记得释放

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//GetMemory内部进行了malloc操作,在Test函数中使用这块空间,
//通过地址传递,可以得到malloc开辟空间的起始地址,记得在Test函数释放
void GetMemory(char** p)
{
  *p = (char*)malloc(100);
}
void Test(void)
{
  char* str = NULL;
  //调用GetMemory,动态开辟100个字节的空间(传址调用,形参影响实参)
  GetMemory(&str);
  //判断malloc是否开辟成功
  if (NULL == str)
  {
    //开辟失败,打印错误信息,并退出
    perror("malloc");
    return ;
  }
  //将hello world拷贝到malloc开辟的空间
  strcpy(str, "hello world");
  //打印拷贝后的内容
  printf(str);
  //使用完动态开辟的内存,free释放
  free(str);
  //free不会改变str的值,防止非法访问内存,将其设置为NULL
  str = NULL;
}
int main()
{
  Test();
  return 0;
}

代码修改2:通过函数的返回值得到malloc开辟空间的起始地址,使用完记得释放

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//GetMemory内部进行了malloc操作,在Test函数中使用这块空间,
//通过返回其malloc的返回值可以得到malloc开辟空间的起始地址,记得在Test函数释放
char* GetMemory()
{
  char* p = (char*)malloc(100);
  return p;
}
void Test(void)
{
  char* str = NULL;
  //调用GetMemory,动态开辟100个字节的空间
  str = GetMemory();
  //判断malloc是否开辟成功
  if (NULL == str)
  {
    //开辟失败,打印错误信息,并退出
    perror("malloc");
    return;
  }
  //将hello world拷贝到malloc开辟的空间
  strcpy(str, "hello world");
  //打印拷贝后的内容
  printf(str);
  //使用完动态开辟的内存,free释放
  free(str);
  //free不会改变str的值,防止非法访问内存,将其设置为NULL
  str = NULL;
}
int main()
{
  Test();
  return 0;
}

题目2:

       下面代码存在什么问题,请你指出问题并修改。

#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;
}

运行结果:

打印结果为什么不是hello world呢?(小知识:0xcccc打印就是烫)

分析:

1、程序从main开始,运行之后调用Test函数;

       2、进入Test函数,①先创建一个字符指针变量str,并赋初值为NULL;②调用GetMemory函数,(因为形参为void,所以不传参),将其返回值赋值给str;

       3、进入GetMemory函数,定义了一个局部变量的数组(因为在栈区申请的空间),返回数组的数组名,即返回数组首元素地址(因为数组在该函数的函数栈帧创建,函数执行结束后会自动释放,所以返回了的数组首元素地址已经没有意义了,再通过该地址去访问空间就会造成非法访问内存问题。);函数执行完,直接回到Test函数中的GetMemory调用。

       4、GetMemory调用完将返回值赋值给str,继续向下执行,调用printf,但是传的实参str指向的那块空间已经还给操作系统,所以造成非法访问内存,打印的就是烫烫……

图示:

问题归纳:

问题:

       return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。(自动销毁就是把空间还给操作系统,空间返回给了操作系统,再使用空间就会造成非法访问内存,所以返回的指针或者引用就没有实际意义了。)。

解决方案:

       1、将局部变量改存在静态区(存在静态区的数据,创建好后,直到程序结束才释放):

              (1)关键字static修饰局部变量,被static修饰的局部变量,改变了局部变量的存放位置,静态局部变量存放在静态区。

               (2)使用字符指针指向常量字符串,常量字符串存储在静态区。

       2、将局部变量拷贝到堆区(存在堆区的数据,创建好后,①程序结束后系统回收;②free自己回收)  

代码修改1:关键字static修饰局部变量

#include<stdio.h>
//创建一个静态局部数组变量,返回其首地址
char* GetMemory(void)
{
  //静态局部变量:存放在静态区,它的生命周期变长,直到程序结束才释放
  static char p[] = "hello world";
  //返回字符串的首地址
  return p;
}
void Test(void)
{
  char* str = NULL;
  //调用GetMemory函数,得到字符串的首地址
  str = GetMemory();
  //打印字符串
  printf(str);
}
int main()
{
  Test();
  return 0;
}

代码修改2:使用字符指针指向常量字符串

#include<stdio.h>
//创建一个常量字符指针,指向一个常量字符串,返回这个字符指针
const char* GetMemory(void)
{
  //常量字符串存储在静态区,直到程序结束才释放
  //常量字符串出现在表达式中,这个常量字符串的值就是首字符的地址
  const char* p = "hello world";
  //返回字符串的首地址
  return p;
}
void Test(void)
{
  const char* str = NULL;
  //调用GetMemory函数,得到字符串的首地址
  str = GetMemory();
  //打印字符串
  printf(str);
}
int main()
{
  Test();
  return 0;
}

代码修改3:将局部变量拷贝到堆区

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//GetMemory内部进行malloc操作,并赋初值“hello world”,之后在Test中打印
//通过返回其malloc的返回值得到“hello world”的首地址,记得在Test中释放
char* GetMemory(void)
{
  char p[] = "hello world";
  //计算数组p的大小
  int sz = sizeof(p);
  //开辟sz个字节的空间
  char* ptr = (char*)malloc(sz);
  //判断是否开辟成功
  if (NULL == ptr)
  {
    //打印错误信息
    perror("malloc");
    exit(0);
  }
  //将数组p拷贝到动态内存中
  strcpy(ptr, p);
  //返回malloc的返回值
  return ptr;
}
void Test(void)
{
  char* str = NULL;
  //调用GetMemory函数,得到malloc的返回值
  str = GetMemory();
  //打印字符串
  printf(str);
  //释放
  free(str);
  str = NULL;
}
int main()
{
  Test();
  return 0;
}

题目3:

       下面代码存在什么问题,请你指出问题并修改。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
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、程序从main开始,运行之后调用Test函数;

       2、进入Test函数,①先创建一个字符指针变量str并为其赋初值为NULL;②调用GetMemory函数第一个参数为地址传递(形参影响实参,解引用可以间接操控实参,得到开辟空间的起始地址),第二个参数为值传递(形参不影响实参,决定开辟空间的大小);

       3、进入GetMemory函数,GetMemory函数内部开辟好空间,在Test中使用开辟的空间,记得释放,因为函数类型为void,所以执行完之后直接回到Test函数中的GetMemory调用;

       4、GetMemory调用完,继续向下执行,①调用strcpy将“hello”拷贝到malloc开辟的空间(使用malloc开辟的空间前完了判断是否开辟成功);②调用printf打印开辟空间的内容,打印完后直接回到main函数中(忘记使用完动态空间之后,free释放,如果程序一直运行,内存泄漏)。


问题归纳:

问题1:

       动态开辟的空间,使用前,忘记判断是否开辟成功,造成非法访问。

解决方案:

       使用前使用if语句判断其返回值是否为空指针。

问题2:

       动态开辟的空间,忘记了释放内存(如果程序一直运行),造成内存泄漏。

解决方案:

       1、动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则就会有错误。(使用完动态开辟的内存使用完一定要释放,并且释放完还要将指针设置为NULL,防止非法访问内存。)。

       2、使用动态开辟的空间,最好注释空间是否释放

代码修改:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//GetMemory函数通过第一个参数间接返回开辟空间的地址,
// 通过第二个参数确定开辟空间的大小。
// 在GetMemory函数内部创建好空间,在Test中使用,记得释放空间
void GetMemory(char** p, int num)
{
  *p = (char*)malloc(num);
}
void Test(void)
{
  char* str = NULL;
  GetMemory(&str, 100);
  //判断malloc是否开辟成功
  if (NULL == str)
  {
    return;
  }
  //使用
  strcpy(str, "hello");
  printf(str);
  //释放
  free(str);
  str = NULL;
}
int main()
{
  Test();
  return 0;
}

题目4:

       下面代码存在什么问题,请你指出问题并修改。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
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、从main开始,运行后调用Test函数;

       2、进入Test函数,①malloc动态申请100个字节的空间,并将返回值赋给str;②调用strcpy函数将“hello”拷贝到动态申请的空间中(使用前未判断malloc是否申请空间成功);③使用完free释放malloc的空间(free释放完空间之后,不会改变指针str的值,未将其设置为NULL,后面使用到str还能找到释放的那块空间,造成非法访问内存);④free释放完,str的值不为空,条件为真,执行if控制的语句,调用strcpy和printf,使用到了已经释放的空间,造成非法访问空间。

问题归纳:

问题1:

       动态开辟的空间,使用前,忘记判断是否开辟成功,造成非法访问。

解决方案:

       使用前使用if语句判断其返回值是否为空指针。

问题2:

       free释放了内存,却仍然继续使用它(free释放完内存,不会改变指针的指向),造成非法访问内存。

解决方案:

       free释放完内存,将指针设置为NULL,防止非法访问内存。

代码修改:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{
  char* str = (char*)malloc(100);
  //使用前判断是否开辟成功
  if (NULL == str)
  {
    return;
  }
  //使用
  strcpy(str, "hello");
  //释放
  free(str);
  //free释放并不会改变指针指向,防止非法访问内存,将其设置为NULL
  str = NULL;
  if (str != NULL)
  {
    strcpy(str, "world");
    printf(str);
  }
}
int main()
{
  Test();
  return 0;
}

加油站:C/C++程序内存分配的区域:

内存图:


C/C++程序内存分配的几个区域:

   1、栈区 (stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中。效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2、堆区(heap):动态内存分配。一般由程序员分配释放,生存期由我们决定,非常灵活,但问题也最多。若程序员自己不释放,程序结束时可能由OS回收。分配方式类似于链表。

3、数据段(静态区):内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,程序结束后由系统释放。例如全局变量、static变量。

4、代码段(静态区):存放函数体(类成员函数和全局函数)的二进制代码。也可能包含一些只读的常数变量,如常量字符串。

在之前我们就学过了static关键字修饰的局部变量,现在我们重新回顾:

       1、普通的局部变量是放在栈区上的,这种局部变量进入作用域创建,出了作用域释放。

       2、但是局部变量被static修饰后,这种变量就放在静态区,放在静态区的变量,创建好后,直到程序结束才释放。

      3、本质上:static的修改改变了局部变量的存储位置,因为存储位置的差异,使得执行效果不一样。

       注意:被static修饰是不影响作用域的!!!但是生命周期发生了变化,变长了。

1、栈区:变量空间进入作用域创建,出作用域就释放。

2、堆区:变量空间由程序员自己决定,或者程序结束由系统回收。

3、静态区:变量空间创建好之后,直到程序结束才释放。

相关文章
|
5月前
|
存储 C++
有关【指针运算】的经典笔试题
有关【指针运算】的经典笔试题
31 4
|
5月前
|
机器学习/深度学习 人工智能 C语言
|
5月前
|
程序员 C语言 C++
C语言学习记录——动态内存习题(经典的笔试题)、C/C++中程序内存区域划分
C语言学习记录——动态内存习题(经典的笔试题)、C/C++中程序内存区域划分
128 0
|
6月前
|
C语言
汉诺塔————经典递归问题(C语言实现)
汉诺塔————经典递归问题(C语言实现)
127 0
详解7道经典指针运算笔试题!
详解7道经典指针运算笔试题!
|
程序员 C语言
初阶函数经典例题(2)
初阶函数经典例题(2)
|
6月前
|
算法 C语言
C语言栈的迷宫求解讲解
C语言栈的迷宫求解讲解
41 0
|
C语言 C++
【C语言】8道经典指针笔试题(深度解剖)
【C语言】8道经典指针笔试题(深度解剖)
127 0
|
C语言
【C语言】经典指针笔试题(深度解剖)(下)
【C语言】经典指针笔试题(深度解剖)(下)
60 0