c++ reference 行为浅析
in Solutions with 0 comment

c++ reference 行为浅析

in Solutions with 0 comment

c++ reference 行为浅析

背景

先前看过的参考资料都说c++的reference是一种语法糖,日常写c++时候也广泛应用了引用去代替指针提高代码的可读性。但是在使用上引用还是有点限制:

探究方式

在不开优化的情况下,用clang将c++语句生成为llvm中间代码

clang++ -S -emit-llvm test.cpp -O0

分析

先上最原始的传值

int moo(){
    int a=100;
    return a;
}

得到的IR为

define i32 @_Z3moov() #0 {
  %1 = alloca i32, align 4
  store i32 100, i32* %1, align 4
  %2 = load i32, i32* %1, align 4
  ret i32 %2
}

开辟了一个4B的int对象,把100存到这个对象(a)的地址(&a)指向的玩意中(*&a),从&a这个地方把值拿出来,放到一个新开辟的4B对象上,然后返回这个新的玩意(%2)。

接下来看看指针

int* doo(){
    int a=100;
    return &a;
}
define i32* @_Z3doov() #0 {
  %1 = alloca i32, align 4
  store i32 100, i32* %1, align 4
  ret i32* %1
}

这边在生成a这个对象并赋值之后就直接把a对象的地址返回给caller了。

再看看reference

int& foo(){
    int a=100;
    return a;
}
define dereferenceable(4) i32* @_Z3foov() #0 {
  %1 = alloca i32, align 4
  store i32 100, i32* %1, align 4
  ret i32* %1
}

这边函数体内的行为可以说和指针一毛一样,返回a对象的地址。但是函数的返回名称不一样,reference那边叫做dereferenceable(4) i32*。这两个什么区别呢,得看IR转汇编。

_Z3foov:                                # @_Z3foov
    .cfi_startproc
# %bb.0:
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset rbp, -16
    mov    rbp, rsp
    .cfi_def_cfa_register rbp
    lea    rax, [rbp - 4]
    mov    dword ptr [rbp - 4], 100
    pop    rbp
    ret
.Lfunc_end0:
    .size    _Z3foov, .Lfunc_end0-_Z3foov
    .cfi_endproc
                                        # -- End function
    .globl    _Z3doov                 # -- Begin function _Z3doov
    .p2align    4, 0x90
    .type    _Z3doov,@function
_Z3doov:                                # @_Z3doov
    .cfi_startproc
# %bb.0:
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset rbp, -16
    mov    rbp, rsp
    .cfi_def_cfa_register rbp
    lea    rax, [rbp - 4]
    mov    dword ptr [rbp - 4], 100
    pop    rbp
    ret
.Lfunc_end1:
    .size    _Z3doov, .Lfunc_end1-_Z3doov
    .cfi_endproc
                                        # -- End function
    .globl    _Z3moov                 # -- Begin function _Z3moov
    .p2align    4, 0x90
    .type    _Z3moov,@function

没啥区别啊,愁。

再分析一个例子

void f1(){
    int a=100;
    int& b=a;
}

void f2(){
    int a=100;
    int* b=&a;
}
; Function Attrs: noinline nounwind optnone uwtable
define void @_Z2f1v() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32*, align 8
  store i32 100, i32* %1, align 4
  store i32* %1, i32** %2, align 8
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define void @_Z2f2v() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32*, align 8
  store i32 100, i32* %1, align 4
  store i32* %1, i32** %2, align 8
  ret void
}

这两种行为也是一样的。如果要生成整型变量,我们得开辟空间,给这个空间一个符号。如果要生成指针,我们也得开辟一个装得下64位机地址的空间,然后把这个地址放进符号表。如果生成一个引用,我们还得开辟一个装地址的空间,然后把原先对象的地址装进去,然后把这个装地址的空间放进符号表。clang转成llvm之后,指针和引用真没什么区别。

总结下:
指针和引用其实都是符号表里的一个老哥,这个老哥存的是别人的地址。我们拿到的指针拿到的是一个暴露的对象,编译器允许我们对指针变量进行p进行dereference获得其地址,但是编译器禁止我们对&b再进行解引用获得其值的地址。这是一个符号表暴露的问题。

Responses