delete 会不会锁表_c++ delete/delete[] 真正的区别在那?

问题描述:

有一次review代码,看到一个类似这样的代码(代码已简化),是否有问题?

struct Box {
 int a;
 int b;
};
// allocate a char array
char* buffer = new char[sizeof(Box) * 5];
// cast a Box array pointer
Box *p = reinterpret_cast<Box *>(buffer);

//free the pointer? is it bad?
delete p;

上述代码通过new char[xx] 一个字符数组,然后强制转为一个结构体指针,然后直接进行释放?在学C++语言的时候,我们被告诫new/delete,new xx[]/delete []p要配对,否则会出问题。但是运行上面代码,并不会发生什么异常或者内存泄露。

为了搞清楚这个问题,需要考虑两方面内容:编译器做了什么和标准库做了什么?

c++标准库做了什么:

为了简化问题,这个地方new/delete, new xx[]/delete []都是C++ Standard Library缺省版本,不考虑自定义的对操作符重载情况;我们以Android系统使用的libcxx库来看看new/delete

libcxx(https://github.com/llvm/llvm-project/tree/master/libcxx/)

libcxxabi:https://github.com/llvm/llvm-project/tree/master/libcxxabi/

libcxx中定义的new/delete等函数week属性,在android系统上会被libcxxabi里面的实现进行覆盖,不过两者差异不大。

_LIBCXXABI_WEAK
void
operator delete(void* ptr) _NOEXCEPT
{
    if (ptr)
        ::free(ptr);
}

_LIBCXXABI_WEAK
void
operator delete[] (void* ptr) _NOEXCEPT
{
    ::operator delete(ptr);
}

标准库里面实现delete等操作是比较直观的,delete[]调用就是delete,然后调用就是c库的free函数,从基础库实现看,上面调用并没有什么问题。

编译器做了什么?

学过C++都知道,针对C++里面的new A() 不仅仅是分配对象,还会调用对应构造函数,析构函数,因此我们还需要去了解一下编译器做了什么事情?下面我们以clang/llvm编译器针对对象的分配都做来那些事情?看一下不同类型产生的llvm ir都是什么?

clang -S -emit-llvm main.cc 

struct Struct {
 int a;
 int b;
};

class Object {
public:
  Object() {
    a = 1;
  }
  ~Object() {
  }

private:
 int a;
};


int main() {
  // Primte type
  int *a = new int[5];
  // Struct type
  Struct *s = new Struct[5];
  // Object Type
  Object *o = new Object[5];

  delete []a;
  delete []s;
  delete []o;

  return 0;
}
~    

//llvm ir
  %1 = alloca i32, align 4

  %2 = alloca i32*, align 8  //局部变量a
  %3 = alloca %struct.Struct*, align 8 //局部变量s
  %4 = alloca %class.Object*, align 8  //局部变量o

  store i32 0, i32* %1, align 4
  %5 = call i8* @_Znam(i64 20) #4 //申请5个int类型20字节内存
  %6 = bitcast i8* %5 to i32*
  store i32* %6, i32** %2, align 8 //存储在a

  %7 = call i8* @_Znam(i64 40) #4  //申请40个字节的内存
  %8 = bitcast i8* %7 to %struct.Struct*
  store %struct.Struct* %8, %struct.Struct** %3, align 8 //存储在s中

  %9 = call i8* @_Znam(i64 28) #4  //每个类5*4个字节+extra8个字节

  %10 = bitcast i8* %9 to i64*
  store i64 5, i64* %10, align 8   //5存储第一个i64类型,表示长度

  %11 = getelementptr inbounds i8, i8* %9, i64 8
  %12 = bitcast i8* %11 to %class.Object*
  %13 = getelementptr inbounds %class.Object, %class.Object* %12, i64 5
  br label %14

; <label>:14:                                     ; preds = %14, %0
  %15 = phi %class.Object* [ %12, %0 ], [ %16, %14 ]
  call void @_ZN6ObjectC1Ev(%class.Object* %15)
  %16 = getelementptr inbounds %class.Object, %class.Object* %15, i64 1
  %17 = icmp eq %class.Object* %16, %13
  br i1 %17, label %18, label %14

从llvm ir可以看出创建prime,struct,object在new的时候做的不一样,尤其object类型,会多申请一个额外的内存存储元素的个数。

; <label>:26:                                     ; preds = %23
  %27 = bitcast %struct.Struct* %24 to i8*
  call void @_ZdaPv(i8* %27) #5
  br label %28

//针对object释放,会先读取Object长度;
; <label>:31:                                     ; preds = %28
  %32 = bitcast %class.Object* %29 to i8*
  %33 = getelementptr inbounds i8, i8* %32, i64 -8
  %34 = bitcast i8* %33 to i64*
  %35 = load i64, i64* %34, align 4
  %36 = getelementptr inbounds %class.Object, %class.Object* %29, i64 %35
  %37 = icmp eq %class.Object* %29, %36
  br i1 %37, label %42, label %38

; <label>:38:                                     ; preds = %38, %31
  %39 = phi %class.Object* [ %36, %31 ], [ %40, %38 ]
  %40 = getelementptr inbounds %class.Object, %class.Object* %39, i64 -1
  call void @_ZN6ObjectD1Ev(%class.Object* %40)
  %41 = icmp eq %class.Object* %40, %29
  br i1 %41, label %42, label %38

clang中如果不使用delete [],使用delete产生ir会发现Struct基本没有变化,但是Object很大差别,无法识别是数组类型产生错误:

; <label>:26:                                     ; preds = %23
  %27 = bitcast %struct.Struct* %24 to i8*
  call void @_ZdlPv(i8* %27) #5
  br label %28

; <label>:28:                                     ; preds = %26, %23
  %29 = load %class.Object*, %class.Object** %4, align 8
  %30 = icmp eq %class.Object* %29, null
  br i1 %30, label %33, label %31

; <label>:31:                                     ; preds = %28
  call void @_ZN6ObjectD1Ev(%class.Object* %29)
  %32 = bitcast %class.Object* %29 to i8*
  call void @_ZdlPv(i8* %32) #5
  br label %33

对于Prime和Struct Plain类型,基本没有差异,但是对于Object类型是错误的。

问题结论:

对于起初给的例子来看,因为都是简单的Struct/Prime类型,并不会出错,如果是Object具备构造函数的类型,就会出现内存泄露等各种潜在等错误,建议大家还是按照标准等写法去写。

new and delete Operators​docs.microsoft.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值