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.内存泄漏的场景有哪些?

内存泄漏的场景:

  • mallocfree未成对出现;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
  • 只能在栈上生成对象:将 newdelete重载为 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处理不了的。