operator new:void*到T*的转换

在C++14标准(C++98/11也一样)中,在Annex C Compatibility里有这么一条:

Change: Converting void* to a pointer-to-object type requires casting

1
2
3
4
5
char a[10];
void* b=a;
void foo() {
char* c=b;
}

ISO C will accept this usage of pointer to void being assigned to a pointer to object type. C ++ will not.

但是为什么operator new()会返回void*且不用显式转换为T*就能赋值给T*呢?

global scopeoperator new基本原型为:

1
2
void* operator new ( std::size_t count );
void* operator new[]( std::size_t count );

这么写不会有任何警告:

1
2
3
struct A{};
A* ap=new A;
delete ap;

自己在类内重载一个operator new返回void*也不会有问题:

1
2
3
4
5
6
struct A{
void* operator new(std::size_t x){ return malloc(x); }
};
A* ap=new A; // OK
delete ap;

但是使用std::malloc来分配再赋值就有问题:

1
2
// error: cannot initialize a variable of type 'A *' with an rvalue of type 'void *'
A* ap=std::malloc(sizeof(A));

编译器也是支持C++14标准的Clang 3.9...

我通过使用LLVM-IR分析发现,调用operator new时编译器会自动将void*转换为T*:

1
2
3
4
5
6
7
struct A{};
int main()
{
A *z=new A;
delete z;
}

其LLVM-IR为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
define i32 @main() #4 {
%1 = alloca i32, align 4
%2 = alloca %struct.A*, align 8
store i32 0, i32* %1, align 4
%3 = call i8* @_Znwy(i64 1) #7
%4 = bitcast i8* %3 to %struct.A*
store %struct.A* %4, %struct.A** %2, align 8
%5 = load %struct.A*, %struct.A** %2, align 8
%6 = icmp eq %struct.A* %5, null
br i1 %6, label %9, label %7
; <label>:7: ; preds = %0
%8 = bitcast %struct.A* %5 to i8*
call void @_ZdlPv(i8* %8) #8
br label %9
; <label>:9: ; preds = %7, %0
%10 = load i32, i32* %1, align 4
ret i32 %10
}
; Function Attrs: nobuiltin
declare noalias i8* @_Znwy(i64) #5
; Function Attrs: nobuiltin nounwind
declare void @_ZdlPv(i8*) #6

其中最重要的就是%4的操作:

1
2
3
4
5
6
# 调用new获得内存地址
%3 = call i8* @_Znwy(i64 1) #7
# 将上一步获得的地址转换为struct.A*
%4 = bitcast i8* %3 to %struct.A*
# 将转换后的地址存储为我们声明的指针
store %struct.A* %4, %struct.A** %2, align 8

经过测试,自己在类内重载operator new也会具有转换。

1
2
3
4
5
6
7
8
9
struct A{
void* operator new(std::size_t x){ return malloc(x); }
};
int main()
{
A *z=new A;
delete z;
}

其LLVM-IR代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
define i32 @main() #4 {
%1 = alloca i32, align 4
%2 = alloca %struct.A*, align 8
store i32 0, i32* %1, align 4
%3 = call i8* @_ZN1AnwEy(i64 1)
%4 = bitcast i8* %3 to %struct.A*
store %struct.A* %4, %struct.A** %2, align 8
%5 = load %struct.A*, %struct.A** %2, align 8
%6 = icmp eq %struct.A* %5, null
br i1 %6, label %9, label %7
; <label>:7: ; preds = %0
%8 = bitcast %struct.A* %5 to i8*
call void @_ZdlPv(i8* %8) #6
br label %9
; <label>:9: ; preds = %7, %0
%10 = load i32, i32* %1, align 4
ret i32 %10
}
; Function Attrs: uwtable
define linkonce_odr i8* @_ZN1AnwEy(i64) #0 comdat align 2 {
%2 = alloca i64, align 8
store i64 %0, i64* %2, align 8
%3 = load i64, i64* %2, align 8
%4 = call i8* @malloc(i64 %3)
ret i8* %4
}
; Function Attrs: nobuiltin nounwind
declare void @_ZdlPv(i8*) #5
declare i8* @malloc(i64) #1

可以看到%4处仍然具有void*struct.A*的转换。
我没有在标准中找到operator new应该会转换void*T*类型的显式要求,但是标准中提到了下面这样的描述:

The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).

全文完,若有不足之处请评论指正。

扫描二维码,分享此文章

本文标题:operator new:void*到T*的转换
文章作者:ZhaLiPeng
发布时间:2017年05月22日 18时03分
本文字数:本文一共有770字
更新历史: Blame, History  文本模式: .md Raw
原始链接:https://imzlp.me/posts/21564/
许可协议: CC BY-NC-SA 4.0
转载请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!