
【C++】 内存管理面试题
1.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。
2.内存泄漏的场景有哪些?
内存泄漏的场景:
-
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等内存泄漏检测工具。
3.如何构造一个类,使得只能在堆上或只能在栈上分配内存?
- 只能在堆上分配内存:将析构函数声明为
private
; - 只能在栈上生成对象:将
new
和delete
重载为private
。
4.浅拷贝和深拷贝有什么区别?
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存;而深拷贝会创造一个相同的对象,新对象与原对象不共享内存,修改新对象不会影响原对象。
5.字节对齐的原则是什么?
- 从偏移为0的位置开始存储;
- 如果没有定义
#pragma pack(n)
sizeof
的最终结果必然是结构内部最大成员的整数倍,不够补齐;- 结构内部各个成员的首地址必然是自身大小的整数倍;
- 如果定义了
#pragma pack(n)
sizeof
的最终结果必然必然是min[n,结构内部最大成员]
的整数倍,不够补齐;- 结构内部各个成员的首地址必然是
min[n,自身大小]
的整数倍。
6.结构体内存对齐问题
- 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
- 未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)
c++11以后引入两个关键字 alignas
与 [alignof]
。其中 alignof
可以计算出类型的对齐方式,alignas
可以指定结构体的对齐方式。
但是 alignas
在某些情况下是不能使用的,具体见下面的例子:
// alignas 生效的情况
struct Info {
uint8_t a;
uint16_t b;
uint8_t c;
};
std::cout << sizeof(Info) << std::endl; // 6 2 + 2 + 2
std::cout << alignof(Info) << std::endl; // 2
struct alignas(4) Info2 {
uint8_t a;
uint16_t b;
uint8_t c;
};
std::cout << sizeof(Info2) << std::endl; // 8 4 + 4
std::cout << alignof(Info2) << std::endl; // 4
alignas
将内存对齐调整为4个字节。所以 sizeof(Info2)
的值变为了8。
// alignas 失效的情况
struct Info {
uint8_t a;
uint32_t b;
uint8_t c;
};
std::cout << sizeof(Info) << std::endl; // 12 4 + 4 + 4
std::cout << alignof(Info) << std::endl; // 4
struct alignas(2) Info2 {
uint8_t a;
uint32_t b;
uint8_t c;
};
std::cout << sizeof(Info2) << std::endl; // 12 4 + 4 + 4
std::cout << alignof(Info2) << std::endl; // 4
若 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
struct Info {
uint8_t a;
uint32_t b;
uint8_t c;
} ONEBYTE_ALIGN;
#if defined(__GNUC__) || defined(__GNUG__)
#undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
#pragma pack(pop)
#undef ONEBYTE_ALIGN
#endif
std::cout << sizeof(Info) << std::endl; // 6 1 + 4 + 1
std::cout << alignof(Info) << std::endl; // 6
确定结构体中每个元素大小可以通过下面这种方法:
#if defined(__GNUC__) || defined(__GNUG__)
#define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
#define ONEBYTE_ALIGN
#pragma pack(push,1)
#endif
/**
* 0 1 3 6 8 9 15
* +-+---+-----+---+-+-------------+
* | | | | | | |
* |a| b | c | d |e| pad |
* | | | | | | |
* +-+---+-----+---+-+-------------+
*/
struct Info {
uint16_t a : 1;
uint16_t b : 2;
uint16_t c : 3;
uint16_t d : 2;
uint16_t e : 1;
uint16_t pad : 7;
} ONEBYTE_ALIGN;
#if defined(__GNUC__) || defined(__GNUG__)
#undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
#pragma pack(pop)
#undef ONEBYTE_ALIGN
#endif
std::cout << sizeof(Info) << std::endl; // 2
std::cout << alignof(Info) << std::endl; // 1
这种处理方式是 alignas
处理不了的。
- 感谢你赐予我前进的力量