iOS- 认识LLVM

什么是LLVM

LLVM是架构编译器(compiler)的框架系统,以C++编写而成,用于优化任何编程语言编写的程序的编译时间(compile-time),链接时间(link-time),运行时间 (run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
目前,LLVM已被苹果的开发工具(Xcode),Xilinx Vivado,Facebook,Google等公司采用。

传统编译器的设计
传统编译器设计
  • Frontend(编译器前端)
    编译器前端主要任务是解析源代码。它会进行词法分析,语法分析,语义分析,检测源代码是否存在错误,然后构建抽象语法树(Abstract Syntax Tree, AST),LLVM前端还会生成中间代码(intermediate representation ,IR)。
  • Optimizer(优化器)
    负责进行各种优化。改善代码运行时间,如消除代码冗余计算等。
  • Backend(编译器后端)/CodeGenerator(代码生成器)
    将代码映射到目标指令集。生成机器语言,并进行机器相关的代码优化。
iOS的编译器架构

iOS的编译器架构

ObjectiveC/C/C++使用的编译器前端是Clang,Swift的编译前端是Swift,而它们的后端都是LLVM

LLVM的设计

LLVM的前后端分离设计

当编译器要支持多种源语言或多种硬件架构时,LLVM的优势就显现出来了。它有别于如GCC这样的编译器,由于GCC是作为整体应用程序而设计的,因此他的用途就受到了局限性和灵活性;而LLVM设计最重要的部分是,使用通用的代码表现形式(IR),它是用来在编译器中表示代码的形式。所以LLVM可以为任何编程语言独立编写前端,同样可以为任何硬件架构独立编写后端。

Clang

Clang是LLVM项目中的一个子项目。它是基于LLVM的一个轻量级编译器,诞生之初是为了替代GCC, 提供更快的编译速度。它是负责编译C,C++,Obj-C的编译器,它属于真个LLVM架构中的,编译器前端

通过Clang 感受编译流程

首先通过Xcode创建一个简单的工程, 这里只包含一个main.m文件

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 通过命令行打印main.m的编译阶段:
clang -ccc-print-phases main.m

执行结果:

0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image

注释
0: 输入文件:找到源文件
1: 预处理:替换宏,头文件导入等
2: 编译:进行词法分析,语法分析,检测语法错误,最终生成ir文件
3: 后端:通过一个个的Pass优化,最终生成汇编代码
4: 汇编:生成目标文件
5: 链接:链接需要的动态库和静态库,生成可执行文件
6: 通过不同的架构,生成对应的可执行文件

  • 执行预处理命令
    这个步骤执行的东西比较多,这里#import <Foundation/Foundation.h>注释掉,然后修改下代码:
#import <stdio.h>

#define A 10
typedef int ZZ_INT;

int main(int argc, const char * argv[]) {
    ZZ_INT B = 15;
    printf("result = %d",A + B);
    return 0;
}
clang -E main.m
# 1 "main.m"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 375 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.m" 2
# 11 "main.m"
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/stdio.h" 1 3 4
# 64 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/stdio.h" 3 4
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/_stdio.h" 1 3 4
# 68 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/_stdio.h" 3 4
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 1 3 4
# 630 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 3 4
//省略.......

typedef union {
 char __mbstate8[128];
 long long _mbstateL;
} __mbstate_t;

typedef __mbstate_t __darwin_mbstate_t;
typedef long int __darwin_ptrdiff_t;

//省略...... 

typedef int ZZ_INT;

int main(int argc, const char * argv[]) {
    ZZ_INT B = 15;
    printf("result = %d",10 + B);
    return 0;
}

省略的部分大致为#import <stdio.h>头文件导入的内容,main函数中printf中,原来#define A 10也被直接替换成了10。这里需要注意的是typedef int ZZ_INT;并没有替换。

  • 编译阶段
    -词法分析
    预处理之后就是词法分析。这里会把代码切成一个个的Token
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

执行结果:

annot_module_include '#import <stdio.h>

#define A 10
typedef int ZZ_INT;

int main(int argc, const char * argv[]) {
    ZZ_INT B = 15;
    printf("result = %d",A + B);
    return 0;
}
?'      Loc=<main.m:11:1>
typedef 'typedef'    [StartOfLine]  Loc=<main.m:14:1>
int 'int'    [LeadingSpace] Loc=<main.m:14:9>
identifier 'ZZ_INT'  [LeadingSpace] Loc=<main.m:14:13>
semi ';'        Loc=<main.m:14:19>
int 'int'    [StartOfLine]  Loc=<main.m:16:1>
identifier 'main'    [LeadingSpace] Loc=<main.m:16:5>
l_paren '('     Loc=<main.m:16:9>
int 'int'       Loc=<main.m:16:10>
identifier 'argc'    [LeadingSpace] Loc=<main.m:16:14>
comma ','       Loc=<main.m:16:18>
const 'const'    [LeadingSpace] Loc=<main.m:16:20>
char 'char'  [LeadingSpace] Loc=<main.m:16:26>
star '*'     [LeadingSpace] Loc=<main.m:16:31>
identifier 'argv'    [LeadingSpace] Loc=<main.m:16:33>
l_square '['        Loc=<main.m:16:37>
r_square ']'        Loc=<main.m:16:38>
r_paren ')'     Loc=<main.m:16:39>
l_brace '{'  [LeadingSpace] Loc=<main.m:16:41>
identifier 'ZZ_INT'  [StartOfLine] [LeadingSpace]   Loc=<main.m:17:5>
identifier 'B'   [LeadingSpace] Loc=<main.m:17:12>
equal '='    [LeadingSpace] Loc=<main.m:17:14>
numeric_constant '15'    [LeadingSpace] Loc=<main.m:17:16>
semi ';'        Loc=<main.m:17:18>
identifier 'printf'  [StartOfLine] [LeadingSpace]   Loc=<main.m:18:5>
l_paren '('     Loc=<main.m:18:11>
string_literal '"result = %d"'      Loc=<main.m:18:12>
comma ','       Loc=<main.m:18:25>
numeric_constant '10'       Loc=<main.m:18:26 <Spelling=main.m:13:11>>
plus '+'     [LeadingSpace] Loc=<main.m:18:28>
identifier 'B'   [LeadingSpace] Loc=<main.m:18:30>
r_paren ')'     Loc=<main.m:18:31>
semi ';'        Loc=<main.m:18:32>
return 'return'  [StartOfLine] [LeadingSpace]   Loc=<main.m:19:5>
numeric_constant '0'     [LeadingSpace] Loc=<main.m:19:12>
semi ';'        Loc=<main.m:19:13>
r_brace '}'  [StartOfLine]  Loc=<main.m:20:1>
eof ''      Loc=<main.m:20:2>

词法分析将代码按照单个词 或者 标点符号 分割出来,并标记出了文件名,行号,起始位置,比如这里的
identifier 'main' [LeadingSpace] Loc=<main.m:16:5>
mainmain.m文件下的 第16行 从第5个字符开始。

-语法分析
词法分析完之后就是语法分析,主要是验证语法是否正确。在词法分析的基础上,将单词序列组合成各类语法短语,如'程序','语句','表达式'等,然后将所有的节点组成抽象语法树(Abstract Syntax Tree,AST)。语法分析程序判断源程序在结构上是否正确。

clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

执行结果:

TranslationUnitDecl 0x7feb9b01cc08 <<invalid sloc>> <invalid sloc> <undeserialized declarations>
|-TypedefDecl 0x7feb9b01d4a0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7feb9b01d1a0 '__int128'
|-TypedefDecl 0x7feb9b01d510 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7feb9b01d1c0 'unsigned __int128'
|-TypedefDecl 0x7feb9b01d5b0 <<invalid sloc>> <invalid sloc> implicit SEL 'SEL *'
| `-PointerType 0x7feb9b01d570 'SEL *'
|   `-BuiltinType 0x7feb9b01d400 'SEL'
|-TypedefDecl 0x7feb9b01d698 <<invalid sloc>> <invalid sloc> implicit id 'id'
| `-ObjCObjectPointerType 0x7feb9b01d640 'id'
|   `-ObjCObjectType 0x7feb9b01d610 'id'
|-TypedefDecl 0x7feb9b01d778 <<invalid sloc>> <invalid sloc> implicit Class 'Class'
| `-ObjCObjectPointerType 0x7feb9b01d720 'Class'
|   `-ObjCObjectType 0x7feb9b01d6f0 'Class'
|-ObjCInterfaceDecl 0x7feb9b01d7d0 <<invalid sloc>> <invalid sloc> implicit Protocol
|-TypedefDecl 0x7feb9b01db48 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7feb9b01d940 'struct __NSConstantString_tag'
|   `-Record 0x7feb9b01d8a0 '__NSConstantString_tag'
|-TypedefDecl 0x7feb9a022a00 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7feb9b01dba0 'char *'
|   `-BuiltinType 0x7feb9b01cca0 'char'
|-TypedefDecl 0x7feb9a022ce8 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7feb9a022c90 'struct __va_list_tag [1]' 1 
|   `-RecordType 0x7feb9a022af0 'struct __va_list_tag'
|     `-Record 0x7feb9a022a58 '__va_list_tag'
|-ImportDecl 0x7feb9a023510 <main.m:11:1> col:1 implicit Darwin.C.stdio
|-TypedefDecl 0x7feb9a023568 <line:14:1, col:13> col:13 referenced ZZ_INT 'int'
| `-BuiltinType 0x7feb9b01cd00 'int'
`-FunctionDecl 0x7feb9a023840 <line:16:1, line:20:1> line:16:5 main 'int (int, const char **)'
  |-ParmVarDecl 0x7feb9a0235d8 <col:10, col:14> col:14 argc 'int'
  |-ParmVarDecl 0x7feb9a0236f0 <col:20, col:38> col:33 argv 'const char **':'const char **'
  `-CompoundStmt 0x7feb9a0c3e60 <col:41, line:20:1>
    |-DeclStmt 0x7feb9a0c3c68 <line:17:5, col:18>
    | `-VarDecl 0x7feb9a0c3800 <col:5, col:16> col:12 used B 'ZZ_INT':'int' cinit
    |   `-IntegerLiteral 0x7feb9a0c3868 <col:16> 'int' 15
    |-CallExpr 0x7feb9a0c3dd0 <line:18:5, col:31> 'int'
    | |-ImplicitCastExpr 0x7feb9a0c3db8 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
    | | `-DeclRefExpr 0x7feb9a0c3c80 <col:5> 'int (const char *, ...)' Function 0x7feb9a0c3890 'printf' 'int (const char *, ...)'
    | |-ImplicitCastExpr 0x7feb9a0c3e18 <col:12> 'const char *' <NoOp>
    | | `-ImplicitCastExpr 0x7feb9a0c3e00 <col:12> 'char *' <ArrayToPointerDecay>
    | |   `-StringLiteral 0x7feb9a0c3cd8 <col:12> 'char [12]' lvalue "result = %d"
    | `-BinaryOperator 0x7feb9a0c3d70 <line:13:11, line:18:30> 'int' '+'
    |   |-IntegerLiteral 0x7feb9a0c3d00 <line:13:11> 'int' 10
    |   `-ImplicitCastExpr 0x7feb9a0c3d58 <line:18:30> 'ZZ_INT':'int' <LValueToRValue>
    |     `-DeclRefExpr 0x7feb9a0c3d20 <col:30> 'ZZ_INT':'int' lvalue Var 0x7feb9a0c3800 'B' 'ZZ_INT':'int'
    `-ReturnStmt 0x7feb9a0c3e50 <line:19:5, col:12>
      `-IntegerLiteral 0x7feb9a0c3e30 <col:12> 'int' 0

CompoundStmt,DeclStmt,CallExpr等都是一些节点对应的命令语句。

-生成中间代码IR(intermediate representation)
完成上面步骤之后就要开始生成中间代码(IR)了,代码生成器会将语法树自上而下的遍历逐步翻译成LLVM-IR。通过如下命令生成.ll文本文件来查看IR代码:

clang -S -fobjc-arc -emit-llvm main.m

打开main.ll文件:

; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"

@.str = private unnamed_addr constant [12 x i8] c"result = %d\00", align 1

; Function Attrs: noinline optnone ssp uwtable
define i32 @main(i32, i8**) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca i32, align 4
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  store i32 15, i32* %6, align 4
  %7 = load i32, i32* %6, align 4
  %8 = add nsw i32 10, %7
  %9 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str, i64 0, i64 0), i32 %8)
  ret i32 0
}

declare i32 @printf(i8*, ...) #1

attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}

!0 = !{i32 2, !"SDK Version", [3 x i32] [i32 10, i32 15, i32 4]}
!1 = !{i32 1, !"Objective-C Version", i32 2}
!2 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!3 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!4 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!5 = !{i32 1, !"Objective-C Class Properties", i32 64}
!6 = !{i32 1, !"wchar_size", i32 4}
!7 = !{i32 7, !"PIC Level", i32 2}
!8 = !{!"Apple clang version 11.0.3 (clang-1103.0.32.59)"}

这里的代码看起来就有点汇编的意思了,先简单介绍下这些标识符:
@ 全局标识
% 局部标识
alloca 开辟空间
align 内存对齐
i32 32Bit/4bytes
store 写入内存
load读取数据
call 调用函数
ret 返回

IR的优化:LLVM将优化级别分为-O0,-O1,-O2,-O3,-Os,

clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

bitCode
Xcode7 以后开启bitCode后,苹果会做进一步优化。生成.bc的中间代码。
我们通过优化过的main.ll来生成main.bc代码:

clang -emit-llvm -c main.ll -o main.bc

执行结果:

dec0 170b 0000 0000 1400 0000 d80c 0000
0700 0001 4243 c0de 3514 0000 0700 0000
620c 3024 9696 a6a5 f7d7 7f5d d3b7 4ffb
b7ed e7fd 4f0b 5180 4c01 0000 210c 0000
e602 0000 0b02 2100 0200 0000 1600 0000
0781 2391 41c8 0449 0610 3239 9201 840c
2505 0819 1e04 8b62 8014 4502 4292 0b42
a410 3214 3808 184b 0a32 5288 4870 c421
2344 1287 8c10 4192 0264 c808 b114 2043
4688 20c9 0132 5284 182a 282a 9031 7cb0
5c91 20c5 c800 0000 8920 0000 1000 0000
3222 4809 2064 8504 9322 a484 0493 22e3
84a1 9014 124c 8a8c 0b84 a44c 1040 7304
4832 000a 7304 6040 8048 19c6 2864 aa30
08a1 8180 1c18 34a8 8c00 cc11 8002 0000
5118 0000 5901 0000 1bf8 27f8 ffff ffff
  • 生成汇编代码
    通过最终的main.bcmain.ll,来生成汇编代码:
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s

执行结果:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 15    sdk_version 10, 15, 4
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $32, %rsp
    movl    $0, -4(%rbp)
    movl    %edi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movl    $15, -20(%rbp)
    movl    -20(%rbp), %eax
    addl    $10, %eax
    leaq    L_.str(%rip), %rdi
    movl    %eax, %esi
    movb    $0, %al
    callq   _printf
    xorl    %ecx, %ecx
    movl    %eax, -24(%rbp)         ## 4-byte Spill
    movl    %ecx, %eax
    addq    $32, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "result = %d"

    .section    __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
    .long   0
    .long   64


.subsections_via_symbols

生成的汇编代码也可以进行优化:

clang -Os -S -fobjc-arc main.m -o main.s

优化之后的文件变小了一些,最明显的变化就是汇编指令变少了:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 15    sdk_version 10, 15, 4
    .globl  _main                   ## -- Begin function main
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    leaq    L_.str(%rip), %rdi
    movl    $25, %esi
    xorl    %eax, %eax
    callq   _printf
    xorl    %eax, %eax
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "result = %d"

    .section    __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
    .long   0
    .long   64

.subsections_via_symbols
  • 生成目标文件(汇编器)
    目标文件的生成,是汇编以汇编代码作为输入,将汇编代码转换为机器代码,最终输出目标文件(object file)。
clang -fmodules -c main.s -o main.o

通过nm命令,查看main.o中的符号信息:

nm -nm main.o
                 (undefined) external _printf
0000000000000000 (__TEXT,__text) external _main

这里的_printf是一个undefined external的。
undefined 表示在当前文件暂时找不到符号_printf;
external 表示这个符号是外部可以访问的。
这里也就是说还没有链接动静态库。

  • 生成可执行文件(链接)
    链接器把编译产生的.o文件和.dylib/.a文件链接,生成一个mach-o文件。
clang main.o -o main

这部操作之后就生成了一个execmach-o的可执行文件:

exec可执行文件

再次查看main中的符号信息:

                 (undefined) external _printf (from libSystem)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100000f6f (__TEXT,__text) external _main
0000000100002008 (__DATA,__data) non-external __dyld_private

可以看到libSystem.dylib就链接上去了。

Clang插件

待补充

总结

本文90%内容为摘抄记录,代码部分为实测。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 175,490评论 5 419
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 74,060评论 2 335
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 124,407评论 0 291
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 47,741评论 0 248
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 56,543评论 3 329
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 43,040评论 1 246
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 34,107评论 3 358
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 32,646评论 0 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 36,694评论 1 271
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 32,398评论 2 279
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 33,987评论 1 288
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 30,097评论 3 285
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 35,298评论 3 282
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 27,278评论 0 14
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 28,413评论 1 232
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 38,397评论 2 309
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 38,099评论 2 314

推荐阅读更多精彩内容

  • LLVM 是一个自由软件项目,它是一种编译器基础设施,以 C++ 写成,包含一系列模块化的编译器组件和工具链,用来...
    leejunhui阅读 767评论 0 2
  • 前言 语言类型 我们有很多维度可以将计算机语言进行分类,其中以编译/执行方式为维度,可以将计算机语言分为: 编译型...
    AiLearn阅读 2,347评论 1 6
  • 前言   计算机是只能直接理解机器语言,而不能直接理解高级语言的,所以计算机要执行高级语言编写的程序,就必须要把高...
    xxxxxxxx_123阅读 820评论 4 0
  • LLVM概述 LLVM 是一个自由软件项目,是构架编译器(compiler)的框架系统,以 C++ 写成,用于优化...
    正_文阅读 1,225评论 0 5
  • 1.概念 1.LLVM LLVM是架构编译器(compiler)的框架系统,以c++编写而成,用于优化以任意程序语...
    MonKey_Money阅读 1,075评论 0 3