13道C++面试问答(内存管理)

new/delete和malloc/free之间有什么关系?

 int *p = new int[2];
int *q = (int *)malloc(2*sizeof(int));
  • new与delete直接带具体类型的指针,malloc和free返回void类型的指针。
  • new类型是安全的,而malloc不是。例如int *p = new float[2];就会报错;而int p =
    malloc(2sizeof(int))编译时编译器就无法指出错误来。
  • new一般分为两步:new操作和构造。new操作对应与malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。
  • new调用构造函数,malloc不能;delete调用析构函数,而free不能。
  • malloc/free需要库文件stdlib.h的支持,new/delete则不需要!

「注意」:delete和free被调用后,内存不会立即回收,指针也不会指向空,delete或free仅仅是告诉操作系统,这一块内存被释放了,可以用作其他用途。但是由于没有重新对这块内存进行写操作,所以内存中的变量数值并没有发生变化,出现野指针的情况。因此,释放完内存后,应该讲该指针指向NULL。

delete与delete []有什么区别?

  • 对于简单类型来说,使用new分配后,不管是数组数组还是非数组形式,两种方式都可以释放内存:
int *a = new int(1);
delete a;
int *b = new int(2);
delete [] b;
int *c = new int[11];
delete c;
int *d = new int[12];
delete [] d;

  • 对于自定义类型来说,就需要对于单个对象使用delete,对于对象数组使用delete
    [],逐个调用数组中对象的析构函数,从而释放所有内存; 如果反过来使用,即对于单个对象使用delete
    [],对于对象数组使用delete,其行为是未定义的;
  • 所以,最恰当的方式就是如果用了new,就用delete;如果用了new [],就用delete []。

内存块太小导致malloc和new返回空指针,该怎么处理?

  • 对于malloc来说,需要判断其是否返回空指针,如果是则马上用return语句终止该函数或者exit终止该程序;
  • 对于new来说,默认抛出异常,所以可以使用try…catch…代码块的方式:
try {
    
    
  int *ptr = new int[10000000];
} catch(bad_alloc &memExp) {
    
    
  cerr << memExp.what() << endl;
}

还可以使用set_new_handler函数的方式:

void no_more_memory() {
    
    
  cerr << "Unable to satisfy request for memory" << endl;
  abort();
}
int main() {
    
    
  set_new_handler(no_more_memory);
  int *ptr = new int[10000000];
}

  • 在这种方式里,如果new不能满足内存分配请求,no_more_memory会被反复调用,所以new_handler函数必须完成以下事情:
    让更多内存可被使用:可以在程序一开始执行就分配一大块内存,之后当new_handler第一次被调用,就将这些内存释放还给程序使用;
  • 使用另一个new_handler;
  • 卸除new_handler:返回空指针,这样new就会抛出异常;
  • 直接抛出bad_alloc异常;
  • 调用abort或exit。

内存泄漏的场景有哪些?

内存泄漏的场景:

  • malloc和free未成对出现;new/new []和delete/delete []未成对出现;
    • 在堆中创建对象分配内存,但未显式释放内存;比如,通过局部分配的内存,未在调用者函数体内释放:
char* getMemory() {
    
    
    char *p = (char *)malloc(30);
    return p;
}
int main() {
    
    
    char *p = getMemory();
    return 0;
}

  • 在构造函数中动态分配内存,但未在析构函数中正确释放内存;
  • 未定义拷贝构造函数或未重载赋值运算符,从而造成两次释放相同内存的做法;比如,类中包含指针成员变量,在未定义拷贝构造函数或未重载赋值运算符的情况下,编译器会调用默认的拷贝构造函数或赋值运算符,以逐个成员拷贝的方式来复制指针成员变量,使得两个对象包含指向同一内存空间的指针,那么在释放第一个对象时,析构函数释放该指针指向的内存空间,在释放第二个对象时,析构函数就会释放同一内存空间,这样的行为是错误的;
  • 没有将基类的析构函数定义为虚函数。

判断和定位内存泄漏的方法:在Linux系统下,可以使用valgrind、mtrace等内存泄漏检测工具。

内存的分配方式有几种?

  • 在栈上分配:在执行函数时,局部变量的内存都可以在栈上分配,函数结束时会自动释放;栈内存的分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限;
  • 从堆上分配:就是那些由 new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收
  • 从自由存储区分配:如果说堆是操作系统维护的一块内存,那么自由存储区就是C++中通过new和delete动态分配和释放对象的抽象概念。需要注意的是,自由存储区和堆比较像,但不等价
  • 从常量存储区分配:特殊的存储区,存放的是常量,不可修改;
  • 从全局/静态存储区分配:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量和静态变量又分为初始化的和未初始化的,在C++里面没有这个区分了,它们共同占用同一块内存区,在该区定义的变量若没有初始化,则会被自动初始化,例如int型变量自动初始为0

堆和栈有什么区别?

  • 分配和管理方式不同:
    • 堆是动态分配的,其空间的分配和释放都由程序员控制;
    • 栈是由编译器自动管理的,其分配方式有两种:静态分配由编译器完成,比如局部变量的分配;动态分配由alloca()函数进行分配,但是会由编译器释放;
  • 产生碎片不同:
    • 对堆来说,频繁使用new/delete或者malloc/free会造成内存空间的不连续,产生大量碎片,是程序效率降低;
    • 对栈来说,不存在碎片问题,因为栈具有先进后出的特性;
  • 生长方向不同:
    • 堆是向着内存地址增加的方向增长的,从内存的低地址向高地址方向增长;
    • 栈是向着内存地址减小的方向增长的,从内存的高地址向低地址方向增长;
  • 申请大小限制不同:
    • 栈顶和栈底是预设好的,大小固定;
    • 堆是不连续的内存区域,其大小可以灵活调整

静态内存分配和动态内存分配有什么区别?

  • 静态内存分配是在编译时期完成的,不占用CPU资源;动态内存分配是在运行时期完成的,分配和释放需要占用CPU资源;
  • 静态内存分配是在栈上分配的;动态内存分配是在堆上分配的;
  • 静态内存分配不需要指针或引用类型的支持;动态内存分配需要;
  • 静态内存分配是按计划分配的,在编译前确定内存块的大小;动态内存分配是按需要分配的;
  • 静态内存分配是把内存的控制权交给了编译器;动态内存分配是把内存的控制权给了程序员;
  • 静态内存分配的运行效率比动态内存分配高,动态内存分配不当可能造成内存泄漏。

如何构造一个类,使得只能在堆上或只能在栈上分配内存?

  • 只能在堆上分配内存:将析构函数声明为private;
  • 只能在栈上生成对象:将new和delete重载为private。

浅拷贝和深拷贝有什么区别?

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存;而深拷贝会创造一个相同的对象,新对象与原对象不共享内存,修改新对象不会影响原对象。

字节对齐的原则是什么?

  • 从偏移为0的位置开始存储;
  • 如果没有定义#pragma pack(n)
    • sizeof的最终结果必然是结构内部最大成员的整数倍,不够补齐;
    • 结构内部各个成员的首地址必然是自身大小的整数倍;
  • 如果定义了#pragma pack(n)
    • sizeof的最终结果必然必然是min[n,结构内部最大成员]的整数倍,不够补齐;
    • 结构内部各个成员的首地址必然是min[n,自身大小]的整数倍。

结构体内存对齐问题

  • 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
  • 未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)

c++11以后引入两个关键字 alignas与 [alignof]。其中alignof可以计算出类型的对齐方式,alignas可以指定结构体的对齐方式。

但是alignas在某些情况下是不能使用的,具体见下面的例子:

// alignas 生效的情况


alignas将内存对齐调整为4个字节。所以sizeof(Info2)的值变为了8。

// alignas 失效的情况


若alignas小于自然对齐的最小单位,则被忽略。

  • 如果想使用单字节对齐的方式,使用alignas是无效的。应该使用#pragma
    pack(push,1)或者使用__attribute__((packed))。
#if defined(__GNUC__) || defined(__GNUG__)
  #define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
  #define ONEBYTE_ALIGN
  #pragma pack(push,1)
#endif


确定结构体中每个元素大小可以通过下面这种方法:

#if defined(__GNUC__) || defined(__GNUG__)
  #define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
  #define ONEBYTE_ALIGN
  #pragma pack(push,1)
#endif


这种处理方式是alignas处理不了的。

在C++中,使用malloc申请的内存能否通过delete释放?使用new申请的内存能否用free?

不能。

malloc /free主要为了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作对象都是必须明确大小的。

而且不能用在动态类上。new 和delete会自动进行类型检查和,也不需要自己明确内存大小,malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的。

当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。

猜你喜欢

转载自blog.csdn.net/suli77/article/details/129311308