C/C++ malloc(alloc) free new and delete

malloc

C标准malloc定义

malloc定义在头文件<stdlib.h>

Allocates size bytes of uninitialized storage.
If allocation succeeds, returns a pointer to the lowest (first) byte in the allocated memory block that is suitably aligned for any object type with fundamental alignment.
If size is zero, the behavior is implementation defined (null pointer may be returned, or some non-null pointer may be returned that may not be used to access storage, but has to be passed to free).

malloc is thread-safe: it behaves as though only accessing the memory locations visible through its argument, and not any static storage.
A previous call to free or realloc that deallocates a region of memory synchronizes-with a call to malloc that allocates the same or a part of the same region of memory. This synchronization occurs after any access to the memory by the deallocating function and before any access to the memory by malloc. There is a single total order of all allocation and deallocation functions operating on each particular region of memory. (since C11)

malloc, callocrealloc原型

1
2
3
4
5
6
#include <stdlib.h>

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void *reallocarray(void *ptr, size_t nmemb, size_t size);

malloc, callocrealloc性质

  1. malloc函数的实现中(C标准没有规定),在分配空间的时候,通常分配的空间要比申请的要大一些,这些额外的空间用来记录malloc这片空间的大小,在使用free的时候会用到。
  2. malloc,分配指定字节的内存空间,初始值不定。
  3. calloc,为指定长度的固定数量的对象分配空间,每一个bit都被初始化为0。
  4. realloc,增加或者减少已经分配的内存空间的大小。当这个大小增加时,可能需要将之前分配的空间中的数据移到另一个足够大的区域以便于增加大小,新增加的区域内的值是不确定的。
  5. 这三个函数返回的指针一定是对齐的,保证它可以用于任何对象。比如double的要求最严格,需要从8的倍数的地址单元开始,这三个函数返回的地址一定满足这个要求。
  6. 它们的返回类型都是void*,需要使用强制类型转换。
  7. realloc函数可以增加或者减少之前分配的内存空间的大小。比如分配了一个固定大小的数组,后来发小它不够用了,可以使用realloc对它进行扩充,如果原有的存储后有足够的大小进行扩充,则可以在原存储区的位置上向高地址进行扩充,无需移动原有数组,返回和传入相同的指针。如果原来的内存空间后没有足够的空间,就重新分配一个足够大的内存空间,再将原有数据的内容复制过去,然后释放原来的内存空间,返回新的指针。
  8. realloc传入的参数是存储区的新长度。如果传入的ptr参数是NULL指针,那就退化成了malloc

自己实现一个malloc

???

free

C标准free定义

free定义在头文件<stdlib.h>

Deallocates the space previously allocated by malloc(), calloc(), aligned_alloc, (since C11) or realloc().
If ptr is a null pointer, the function does nothing.
The behavior is undefined if the value of ptr does not equal a value returned earlier by malloc(), calloc(), realloc(), or aligned_alloc() (since C11).
The behavior is undefined if the memory area referred to by ptr has already been deallocated, that is, free() or realloc() has already been called with ptr as the argument and no calls to malloc(), calloc() or realloc() resulted in a pointer equal to ptr afterwards.
The behavior is undefined if after free() returns, an access is made through the pointer ptr (unless another allocation function happened to result in a pointer value equal to ptr)
free is thread-safe: it behaves as though only accessing the memory locations visible through its argument, and not any static storage.

A call to free that deallocates a region of memory synchronizes-with a call to any subsequent allocation function that allocates the same or a part of the same region of memory. This synchronization occurs after any access to the memory by the deallocating function and before any access to the memory by the allocation function. There is a single total order of all allocation and deallocation functions operating on each particular region of memory. (since C11)

free原型

1
2
3
#include <stdlib.h>

void free(void *ptr);

free属性

从标准的定义可以看出来,以下都是未定义的行为:

  1. free的对象不是alloc函数族的返回值;比如

    1
    2
    3
    4
    int *pi = (int*)malloc(10 * sizeof(int));
    pi++;
    //下面就是错误的,因为这个`pi`不是`alloc`函数族的返回值。
    free(pi);

  2. free一个已经被释放过了的块;

  3. 访问一个free已经释放了的块。
    等等。为什么???因为标准并没有定义malloc应该怎么实现,有的内存分配器,malloc实际申请的内存要比传入的参数大,里面存放了额外的数据记录这块内存有多大,一般就是存在指针左边。free的时候,就会读取那个内存块中存放的信息,进行free,所以上面的那些都是未定义的行为。

其他属性

  1. free可以释放ptr指向的内存空间,释放的空间通常送入可用内存池,之后可以通过这三个函数重新分配。
  2. mallocfree底层通常使用sbrk系统调用实现,这个系统调用扩充或者减小进程的堆,虽然sbrk可以扩充或者缩小进程的堆,但是一般mallocfree的实现不会减少进程的内存空间,释放的内存空间保存在malloc池中,而不是交给内核。
  3. 大多数实现分配的空间要比请求的空间大一些,因为需要存储一些管理信息,如block的大小,指向下一个block的指针等等。因此,如果对超过一个分配区域的内存进行读写的话,会造成很严重的错误。
  4. free一个已经释放了的块,free的不是alloc函数的返回值,没有进行free等等,都是未定义的结果。为什么???

new

对于自定义类型而言,new操作符首先调用operator new()函数申请内存,其内部调用的是malloc函数,返回一个void*类型的指针;new还会负责把它转换成自定义对象的指针;然后调用类的构造函数初始化对象;最后返回自定义对象的指针。malloc只负责内存的分配而不会调用类的构造函数数。举个例子:

1
2
3
4
5
6
7
8
Complex *pc = new Complex(1, 2);
//等价于
// 1.内部调用malloc
void *mem = operator new(sizeof(Complex));
// 2.将void *类型指针转换为Complex*类型的
pc = static_cast<Complex*> (mem);
// 3.调用Complex的构造函数
pc->Complex::Complex(1, 2);

  1. 默认初始化。new后面加类型,没有小括号,也没有花括号。
    默认情况下,new分配的对象,不管是单个分配的还是数组中的,都是默认初始化的。这意味着内置类型或组合类型的对象的值是无定义的,而类类型对象将用默认构造函数进行初始化。
  2. 值初始化。类型名字后加()即可,对于内置类型的变量,初始化为0,对于类类型的变量,调用默认构造函数。
  3. 直接初始化。使用初始化列表加对象值,或者小括号加对象值。

对于自定义类型而言,只要一调用new,无论后面有没有加(),那么编译器不仅仅给它分配内存,还调用它的默认构造函数初始化。

delete

  1. delete是和new配对的操作。调用delete操作,编译器实际上会将它转换为两步操作,第一步是调用析构函数;第二部调用opereator delete(),其内部调用了free
  2. 数组的new一定要和数组的delete配对,否则就会出现内存泄露。第一条中说了delete分为两步,第一步其实不会造成内存泄露;而是第二步中,析构函数被调用的次数,delete p只会调用一次析构函数,而delete[] p会调用多次析构函数,调用次数和new时申请的数组大小一样。

malloc vs new

  1. malloc是C语言中的函数,而new是C++的操作符;
  2. malloc返回的是void*类型的指针,需要我们手动进行强制类型转换转换成我们需要的类型,而new返回的是对象类型的指针,类型和对象严格匹配,new是类型安全型操作符。
  3. 在分配内存失败时,malloc会返回NULL,而new会throw on failure。
  4. malloc需要指定申请的内存占多少个字节,而new不需要指定申请内存块的大小,编译器会根据类型计算需要的内存大小;
  5. mallocnew都是申请heap上的内存;
  6. new操作符调用的operator new()函数可以重载(操作符new不能重载),而malloc不能重载。
  7. mallocfreenewdelete必须配套使用。

几个问题:

  1. 什么时候mallocnew会申请内存失败。
  2. new操作符的两个步骤,一个是申请内存,一个是调用构造函数,new的申请内存和malloc的申请内存有什么区别。

参考文献

1.《C++ Primer第五版》
2.https://stackoverflow.com/questions/184537/in-what-cases-do-i-use-malloc-and-or-new
3.https://zhuanlan.zhihu.com/p/47089696?utm_source=wechat_session&utm_medium=social&utm_oi=687606928481730560