C/C++ 笔试面试(1)——内存管理GetMemory

来源:互联网 发布:古埃及神话知乎 编辑:程序博客网 时间:2024/06/14 08:27
1.关于动态申请内存的问题 出现率极高

        程序的局部变量存在于(栈)中,是系统自动分配空间。定义一个 char a;系统会自动在栈上为其开辟空间。由于栈上的空间是自动分配自动回收的,所以栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。
        程序的全局变量存在于(静态存储区)中
        程序动态申请的数据存在于(堆)中,堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。

<1>
       
void GetMemory(char *p)  {      p = (char *)malloc(100);  }    void Test1(void)  {      char *str = NULL;      GetMemory(str);        strcpy(str, "hello world");      printf(str);  //str一直是空,程序崩溃   } 
  请问运行Test1函数会有什么样的结果?
  

答:试题传入GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完 
char *str = NULL;
GetMemory( str );
后的str仍然为NULL;


毛病出在函数GetMemory 中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存


<2>

char *GetMemory(void)  {        char p[] = "hello world";      retrn p;  }  void Test2(void)  {      char *str = NULL;  
    str = GetMemory();          printf(str);  }  
  请问运行Test2函数会有什么样的结果? 
答:可能是乱码。             

     char p[] = "hello world";      
      return p;  
     p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。

   不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡;

<3>

char *GetMemory3(void)  {       return "hello world";  }  void Test3(void)  {      char *str = NULL;      str=GetMemory3();       printf(str);  }  

  请问运行Test3函数会有什么样的结果? 

Test3 中打印hello world,因为返回常量区,而且并没有被修改过。(字符串常量保存在只读的数据段,是全局区域,但不是像全局变量那样保存在普通数据段(静态存储区)。无法对p所指的内存的内容修改,例如p[0] = 'y;这样的修改是错误的。)

<4>

void GetMemory2(char **p, int num)  {      *p = (char *)malloc(num);  }    void Test(void)  {     char *str = NULL;     GetMemory(&str, 100);     strcpy(str, "hello");       printf(str);      }  
请问运行Test函数会有什么样的结果? 
答: 
1)能够输出hello
2Test函数中也未对malloc的内存进行释放。 
3GetMemory避免了试题1的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句 
*p = (char *) malloc( num );
后未判断内存是否申请成功,应加上: 
if ( *p == NULL )
{
   ...//
进行申请内存失败处理 
}

&str是指针的地址,将指针的地址传给形参p,则p也指向str,
所以*p = (char *)malloc(sizeof(char) * num);也就是给p所指向的str分配了内存,所以正确。
<5>

void Test(void)  {      char *str = (char *) malloc(100);      strcpy(str, "hello");      free(str);            if(str != NULL)      {        strcpy(str, "world");        printf(str); //str为野指针,打印的结果不得而知        }  }  

请问运行Test函数会有什么样的结果?

答:执行 
char *str = (char *) malloc(100);
后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变成一个“野”指针,应加上: 
str = NULL;

程序出现了野指针

  野指针只会出现在像C和C++这种没有自动内存垃圾回收功能的高级语言中, 所以java或c#肯定不会有野指针的概念. 当我们用malloc为一个指针分配一个空间后, 用完这个指针,把它free掉,但是没有让这个指针指向NULL或某一个特定的空间。如上面程序一样,将str进行free后,只是释放了指针所指的内存,但指针并没有释放掉,此时指针所指的是垃圾内存;这样的话,if语句永为真,if判断无效。delete也存在同样的问题。

      防止产生野指针:(1)指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。(2)当free或delete后,将指针指向NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。

<6>

char *GetMemory5(void)    {              return "hello";    }    void Test3(void)    {        char *str = NULL;        str = GetMemory5();         printf(str);    }    

结果:输出hello
分析:直接返回常量区。

2.指针形参&引用形参

1.

当函数需要处理数组且函数体不依赖于数组的长度时应使用指针形参,其他情况下应使用引用形参:


2.

指针形参

优点:可以明确地表示函数所操纵的是指向数据元素的指针,而不是数组本身,而且可以使用任意长度的实参数组来调用函数;

缺点:函数体不能依赖于数组的长度,否则容易造成数据内存的越界访问,从而产生错误的结果或者程序崩溃。

引用形参

优点:在函数体中依赖数组的长度是安全的;

缺点:限制了可以传递实参数组,只能使用长度匹配的实参数据来调用函数。


3. C语言/C++ 动态内存分配和释放

一、先来谈谈在C语言下,动态内存分配和释放的特点。

在C语言下究竟如何实现动态内存分配 malloc(), calloc(), realloc(), 在使用这些函数时必须包含其头文件,分别为:<malloc.h>、<stdlib.h>、<alloc.h>,而释放内存的函数为free(),分别探讨他们的异同。


1.malloc 函数 void *malloc( unsigned int size);

在内存的动态存储区中分配一块长度为"size" 字节的连续区域。

如果分配成功,则返回所分配内存空间的首地址,否则返回NULL,申请的内存不会进行初始化。

 “类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。例如: pc=(char *) malloc (100); 


2.calloc 函数 void *calloc(unsigned int num, unsigned int size)

按照所给的数据个数和数据类型所占字节数,分配一个 num * size 连续的空间。 
函数返回该存储区的起始地址。

calloc申请内存空间后,会自动初始化内存空间为 0,但是malloc不会进行初始化,其内存空间存储的是一些随机数据。 

calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。例如: ps=(struct stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。


3. realloc函数:void *realloc(void *ptr, unsigned int size)

动态分配一个长度为size的内存空间,并把内存空间的首地址赋值给ptr,把ptr内存空间调整为size。

申请的内存空间不会进行初始化。

例如p1=(float *)realloc(p1,16);将原先开辟的8个字节调整为16个字节。


4.    free函数: 函数原型为void free(void *ptr)

作用:释放由上面3种函数所申请的内存空间。

参数:ptr:指向需要释放的内存空间的首地址。

函数原型为 void free(void *ptr)其中ptr为存放待释放空间起始地址的指针变量,函数无返回值。

应注意:ptr所指向的空间必须是前述函数所开辟的。

例如free((void *)p1);将上例开辟的16个字节释放。可简写为free(p1);由系统自动进行类型转换。

二、C++语言动态内存分配

 在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

申请和释放堆中分配的存储空间,分别使用new 和 delete 的两个运算符来完成:

指针变量名 = new 类型名(初始化式);

delete 指针名;

例如:int *pi = new int(0)

 

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可以用于申请动态内存和释放内存。

对于非内部数据类型对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free.


C++语言中用new和delete来动态申请和释放内存。
1. 申请单个对象
  int *p;
  p=new int;或者 p=new int(value);
  2. 动态申请数组
  int *p;
  p=new int [100];
  这样可以申请长度为100的数组,但是不能进行初始化。
 3. delete
  int *p, *q;
  p=new int;
  q=new int[10];
  delete p;
  delete [ ]q;







原创粉丝点击