C++ 了解内存分配工作原理(LLVM)
我正在一个玩具编译器上取得进展(第一次),并试图理解如何分配/构造LLVM结构类型。 万花筒教程没有包括甚至没有提到这一点,我不知道我在LLVM源代码/测试中寻找什么来寻找可能的示例 所以我写了一个简单的C++例子,用一种铿锵的方式来倾倒,试图去理解它产生了什么,但说实话,我没有跟上它。对我来说很明显的是函数定义/声明和一些函数调用以及C++ 了解内存分配工作原理(LLVM),c++,memory-management,clang,llvm,llvm-ir,C++,Memory Management,Clang,Llvm,Llvm Ir,我正在一个玩具编译器上取得进展(第一次),并试图理解如何分配/构造LLVM结构类型。 万花筒教程没有包括甚至没有提到这一点,我不知道我在LLVM源代码/测试中寻找什么来寻找可能的示例 所以我写了一个简单的C++例子,用一种铿锵的方式来倾倒,试图去理解它产生了什么,但说实话,我没有跟上它。对我来说很明显的是函数定义/声明和一些函数调用以及memset调用,所以我得到了其中的一部分,但对我来说还不是全部。(P.S我对alloca指令文档的解释是,它创建的任何东西在返回时都会被释放,所以我不能正确使用
memset
调用,所以我得到了其中的一部分,但对我来说还不是全部。(P.S我对alloca指令文档的解释是,它创建的任何东西在返回时都会被释放,所以我不能正确使用它,它基本上只用于局部变量?)
我所做的是:
alloc.cpp
struct Alloc {
int age;
};
//Alloc allocCpy() {
// return *new Alloc();
//}
Alloc *allocPtr() {
return new Alloc();
}
int main() {
Alloc *ptr = allocPtr();
// ptr->name = "Courtney";
// Alloc cpy = allocCpy();
// cpy.name = "Robinson";
// std::cout << ptr->name << std::endl;
// std::cout << cpy.name << std::endl;
return 0;
}
有人能解释一下这段IR发生了什么,它是如何映射到C++的?或者忽略这个特定的示例,我们将/应该如何为一个LLVM StructType分配堆内存,而这个LLVM StructType超出了创建它的函数(如果您觉得很慷慨,那么以后如何释放内存)
我已经注释掉的部分来自我的原始示例,但是作为一个完全的新手,IR的见解就更少了
我对alloca指令文档的解释是它什么都可以
从中创建的在返回时被释放,所以我不能正确使用它,它是
基本上只针对局部变量
对。此外,目前关于LLVM IR的建议是,尽管alloca
可以按照您的预期工作,但优化是另一种情况。他们建议您立即在输入块中分配所有本地人,即使您不允许用户访问这些本地人或这些本地人并不总是包含有意义的数据
堆分配是一项库功能。它不是LLVM或编译器的特性。当您使用newt()
时,编译器只需调用操作符new
来获取内存,然后在那里构造T
。这里面没有魔法。您看到的大多数垃圾都是特定于C++-ABI的,而不是LLVM的任何要求。它最终降低为类似于void*p=malloc(大小);新(p)T()代码>。对于几乎所有类型的T
,这可以归结为一系列存储到p
或调用用户定义的函数
您可以使用所选运行库中的内存分配函数
试图理解如何分配/构造LLVM结构类型
LLVM类型系统不包括构造的概念。这是源语言的概念
就LLVM而言,结构只是一堆位,所有内存位置或多或少都是相同的。如果您希望位是一个特定的东西,那么将您想要的位存储到该位置。如果要将位放在堆上,则调用运行时库堆分配函数并将位存储到该位置
然而,请注意,垃圾收集是一个有点不同的故事,因为w.r.t.正在进行一些尴尬的事情,在堆栈上查找本地标记
为了记录在案,您在理解Clang的LLVM IR时不会走多远。我已经这么做了好几年了,这简直是疯了,你要花那么长时间才能开始掌握,更不用说你不想知道的C++特定的ABI细节了。你会在IRC频道的llvm中进一步询问,或者在这里询问具体问题,而不是尝试进行反向工程。我不建议查看由Clang发出的未优化IR-这太冗长了-O1
使其更具可读性-以下是带有注释的-O1
版本(我还重新排序了两行,使其更具可读性):
%struct.Alloc=type{i32};定义Alloc类型。
定义noalias%struct.Alloc*@_Z8allocPtrv()#0{
%1=尾部调用noalias i8*@_Znwj(i32 4)#2;调用_Znwj(4)。此重新调用i8*。
%3=将i8*%1比特转换为i32*;将返回值转换为i32*(int*)。。。
存储i32 0,i32*%3,对齐4;…并将其内容归零。
%2=将i8*%1比特转换为%struct.Alloc*;将返回值转换为Alloc*。。。
返回%struct.Alloc*%2;…并返回它。
}
; 声明_Znwj函数。这不需要定义,因为它已经定义了
; 在libstdc++中:这是“运算符新建”。通过将此字符串传递给
; C++ Dimangle,例如http://demangler.com/.
宣布诺亚利亚斯i8*@_Znwj(i32)#1
定义i32@main()#0{
%1=尾部调用%struct.Alloc*@_Z8allocPtrv();调用_Z8allocPtrv(定义见上文)。
ret i32 0
}
这是一个新的调用,不是本地分配,因此离开@\u Z8allocPtrv
时它不会被清除。本地分配实际上是在LLVM IR中使用alloca
指令执行的,而不是new
调用
如果您想知道new
是如何工作的,我相信它的标准实现使用了malloc
,它由编译库的编译器翻译成包含系统调用的函数。我明白了,这是有道理的。感谢您的快速回复。这纯粹是巧合。哦,很好,评论非常有用。有一个网站看起来很有希望:
; ModuleID = 'alloc.cpp'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.11.0"
%struct.Alloc = type { i32 }
; Function Attrs: ssp uwtable
define %struct.Alloc* @_Z8allocPtrv() #0 {
entry:
%call = call noalias i8* @_Znwm(i64 4) #3
%0 = bitcast i8* %call to %struct.Alloc*
%1 = bitcast %struct.Alloc* %0 to i8*
call void @llvm.memset.p0i8.i64(i8* %1, i8 0, i64 4, i32 4, i1 false)
ret %struct.Alloc* %0
}
; Function Attrs: nobuiltin
declare noalias i8* @_Znwm(i64) #1
; Function Attrs: nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture, i8, i64, i32, i1) #2
; Function Attrs: ssp uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%ptr = alloca %struct.Alloc*, align 8
store i32 0, i32* %retval
%call = call %struct.Alloc* @_Z8allocPtrv()
store %struct.Alloc* %call, %struct.Alloc** %ptr, align 8
ret i32 0
}
attributes #0 = { ssp uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="core2" "target-features"="+cx16,+sse,+sse2,+sse3,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nobuiltin "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="core2" "target-features"="+cx16,+sse,+sse2,+sse3,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { nounwind }
attributes #3 = { builtin }
!llvm.module.flags = !{!0}
!llvm.ident = !{!1}
!0 = !{i32 1, !"PIC Level", i32 2}
!1 = !{!"clang version 3.7.0 (tags/RELEASE_370/final)"}
%struct.Alloc = type { i32 } ; Define the Alloc type.
define noalias %struct.Alloc* @_Z8allocPtrv() #0 {
%1 = tail call noalias i8* @_Znwj(i32 4) #2 ; Call _Znwj(4). This retuns i8*.
%3 = bitcast i8* %1 to i32* ; Cast the returned value to i32* (int*)...
store i32 0, i32* %3, align 4 ; ...and zero its content.
%2 = bitcast i8* %1 to %struct.Alloc* ; Cast the returned value to Alloc*...
ret %struct.Alloc* %2 ; ...and return it.
}
; Declare the _Znwj function. This doesn't need to be defined since it's already defined
; in libstdc++: this is 'operator new'. You can see this by passing this string through a
; C++ demangler, for example the one at http://demangler.com/.
declare noalias i8* @_Znwj(i32) #1
define i32 @main() #0 {
%1 = tail call %struct.Alloc* @_Z8allocPtrv() ; Call _Z8allocPtrv (Defined above).
ret i32 0
}