C++是一种功能强大的编程语言,得到了广泛的应用和支持,包括来自Microsoft和Apple等大型企业的支持。本文系列将探讨C++的一般特性、不同功能以及其内部机制。将使用llvm/Clang编译器工具链来深入研究这门语言并探索其特性。最好的参考书籍是《Inside the C++ Object Model》,将跟随这本书,从Clang的角度来理解C++。
注意:这不是一个C++初学者教程,也不打算成为初学者教程。可以在网上找到其他书籍或教程进行学习。
在本介绍中,将看到LLVM汇编语言是什么样子的。可以从llvm.org网站的下载链接中安装Clang安装程序。对于LLVM汇编的完整描述,请参考LLVM文档。这里将给出一个简要的介绍,将在需要的时候发现汇编。LLVM汇编是一种类型化的汇编语言。与常规汇编不同,变量代表类型。它被设计为更接近高级语言,并提供更好的优化和安全特性。需要记住的一些事情:
LLVM有一个强大的类型系统,这也是其最重要的特性之一。LLVM定义了一个整数类型为iN,其中N是整数将占用的位数。可以指定1到223-1之间的任何位宽。
可以声明一个向量或数组类型为[元素数量 X 每个元素的大小]。对于字符串“Hello World!”,这使得类型为[13 x i8],假设每个字符是1字节,并考虑了1个额外的字节用于NULL字符。
声明一个全局字符串常量为hello-world字符串如下:
@hello = constant [13 x i8] c"Hello World!\00"
使用constant关键字声明一个常量,后跟类型和值。类型已经在前面讨论过,所以让看看值:首先使用c,然后是双引号中的整个字符串,包括\0,并以0结尾。不幸的是,LLVM文档没有提供为什么字符串需要用c前缀声明,并包括一个NULL字符和0在结尾的解释。如果对探索更多的LLVM特性感兴趣,请参阅资源中的语法文件链接。
LLVM允许声明和定义函数。不会详细介绍LLVM函数的所有特性列表,将专注于最基础的部分。从define关键字开始,后跟返回类型,然后是函数名。一个返回32位整数的简单main定义如下:
define i32 @main() { ; some LLVM assembly code that returns i32 }
函数声明,像定义一样,有很多内容。这里有一个最简单的puts方法声明,它是LLVM等同于printf的:
declare i32 puts(i8*)
以declare关键字开始声明,后跟返回类型,函数名,以及函数的可选参数列表。声明必须在全局范围内。
每个函数都以一个返回语句结束。有两种形式的返回语句:ret
使用call
call i6 @test( i36 %arg1 )
现在知道了足够的知识来开始编译一些示例并进行尝试。在Windows上创建一个名为test.cpp的文件,内容如下:
class A {
int _i;
};
int main() {
A t;
return 1;
}
现在使用clang.exe和以下命令生成“test.ll”文件,该文件包含llvm汇编。
clang.exe -S -emit-llvm test.cpp
生成的代码如下:
%class.A = type { i32 }
; Function Attrs: nounwind
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%t = alloca %class.A, align 4
store i32 0, i32* %retval
ret i32 1
}
如所知,所有局部标识符都以%开头,并且可以包含一个“.”。因此,标识符名称是“class.A”。它应该只包含一个整数。注释以“;”开始。main函数以关键字“define”开始。
Clang还包括一个方便的工具来转储C++AST(抽象语法树),可以分析它。这并不总是适用于任何类型的更广泛用途,但可以从小示例中学习一些基本的东西。给出的示例代码中不会使用任何包含或库,以减少AST或IR的复杂性。
要生成AST,可以给出以下命令:
clang.exe -fcolor-diagnostics -Xclang -ast-dump test.cpp
上述语句将给出以下输出:
JavaScript
TranslationUnitDecl 0x270dc0 <>
|-TypedefDecl 0x2710b0 <> implicit __builtin_va_list
'
char *'
|-TypedefDecl 0x2711b0 col:16
FunPtrType
'
void (*)(void)'
|-CXXRecordDecl 0x2711e0 line:2:7
union U definition
| |-CXXRecordDecl 0x2712b0 col:7 implicit union U
| |-FieldDecl 0x271320 col:5
_i
'
int'
| |-FieldDecl 0x271360 col:7
_f
'
float'
| |-FieldDecl 0x2713a0 col:6
_c
'
char'
| |-FieldDecl 0x2713e0 col:8
_d
'
double'
| |-FieldDecl 0x271420 col:7
_p
'
void *'
| |-FieldDecl 0x271470 col:12
_fp
'
FunPtrType':
'
void (*)(void)'
| |-CXXConstructorDecl 0x271900 col:7 implicit used U
'
void (void) __attribute__((thiscall))'
inline noexcept-unevaluated 0x271900
| |
`
-CompoundStmt 0x271ad8
| `
-CXXConstructorDecl 0x2719e0 col:7 implicit U
'
void (const union U &) __attribute__((thiscall))'
inline noexcept-unevaluated 0x2719e0
|
`
-ParmVarDecl 0x271aa0 col:7 'const union U &'
`
-FunctionDecl 0x2714e0 line:11:5
main
'
int (void)'
`
-CompoundStmt 0x271b50
|-DeclStmt 0x2715e8
| `
-VarDecl 0x271580 col:6 sizeInt
'
int'
|
`
-ImplicitCastExpr 0x2715d8 'int'
| `
-UnaryExprOrTypeTraitExpr 0x2715c0
'
unsigned int'
sizeof
'
int'
|-DeclStmt 0x271678
|
`
-VarDecl 0x271610 col:6 sizefloat 'int'
| `
-ImplicitCastExpr 0x271668
'
int'
|
`
-UnaryExprOrTypeTraitExpr 0x271650 'unsigned int' sizeof 'float'
|-DeclStmt 0x271700
| `
-VarDecl 0x2716a0 col:6 sizeChar
'
int'
|
`
-ImplicitCastExpr 0x2716f0 'int'
| `
-UnaryExprOrTypeTraitExpr 0x2716d8
'
unsigned int'
sizeof
'
char'
|-DeclStmt 0x271788
|
`
-VarDecl 0x271720 col:6 sizeDouble 'int'
| `
-ImplicitCastExpr 0x271778
'
int'
|
`
-UnaryExprOrTypeTraitExpr 0x271760 'unsigned int' sizeof 'double'
|-DeclStmt 0x271818
| `
-VarDecl 0x2717b0 col:6 sizeV
'
int'
|
`
-ImplicitCastExpr 0x271808 'int'
| `
-UnaryExprOrTypeTraitExpr 0x2717f0
'
unsigned int'
sizeof
'
void *'
|-DeclStmt 0x2718a0
|
`
-VarDecl 0x271840 col:6 sizeFP 'int'
| `
-ImplicitCastExpr 0x271890
'
int'
|
`
-UnaryExprOrTypeTraitExpr 0x271878 'unsigned int' sizeof 'FunPtrType':'void (*)(void)'
|-DeclStmt 0x271b10
| `
-VarDecl 0x2718c0 col:4 u
'
union U'
|
`
-CXXConstructExpr 0x271ae8 'union U' 'void (void) __attribute__((thiscall))'
`
-ReturnStmt 0x271b40
`
-IntegerLiteral 0x271b20 'int' 1