c++ goto & label 机制汇编探索

本文将从汇编的层面来探索 goto 机制,提起 goto 可能会引起程序员的恐惧,但面对恐惧才能战胜恐惧,以致可以利用未曾尝试的点。

arm gcc 实测

void fun(){
    label:
        printf("\n");
        goto label;
}
  • 使用 gcc -E 即只走预处理看看:
// 只多了一些头文件的预处理
void fun(){
    label:
        printf("\n");
        goto label;
}
  • 使用 gcc -S 即走到编译这一步看看:
fun():
        push    {fp, lr}
        add     fp, sp, #4
.L4:
        mov     r0, #10
        bl      putchar
        b       .L4

可以看到 goto 的 label 变成了汇编里的 label (.L4),大体逻辑一致。

解释一下 “\n” 去哪了:正是指令当中的 #10,用 ASCII 码可查出对应的是换行符

0000 1010100ALF换行键
  • 使用 gcc -c 即走到汇编(这里指的是动作,是把汇编文件进行汇编解释)这一步看看:
                     ltmp0:
0000000000000000         stp        x29, x30, [sp, #-0x10]!        ; DATA XREF=ltmp2
0000000000000004         mov        x29, sp

                     loc_8:
0000000000000008         adrp       x0, #0x0                       ; 0x18@PAGE, CODE XREF=ltmp0+20
000000000000000c         add        x0, x0, #0x18                  ; 0x18@PAGEOFF
0000000000000010         bl         _printf                        ; _printf
0000000000000014         b          loc_8


        ; Section __cstring
        ; Range: [0x18; 0x1a[ (2 bytes)
        ; File offset : [488; 490[ (2 bytes)
        ; Flags: 0x2
        ;   S_CSTRING_LITERALS

                     l_.str:
0000000000000018         db         "\n", 0

由于已经是二进制的形式了,所以用的是反汇编来看。这里文件格式是 MachO 格式,是苹果所用的,不同平台可能会有些许不同。

同样也是通过 loc_8 这个汇编的 label 来实现,但这个 label 在二进制中长什么样呢?

先看下 b 汇编指令的模样:

image.png

然后在二进制里找 __TEXT,__text 段里的这条指令,在 arm 上是 4 个字节一条指令,注意不是在上面代码的 0x14 来找,因为反汇编显示的是相对于 __TEXT,__text 段的偏移,MachO 文件前面还有一大堆记录文件属性的东西。

二进制中长这样: FDFFFF17
在 iOS 是小端,所以读为 17FFFFFD
则 imm26 = 0x3FFFFFD
这个 imm26 在 b 指令是作为偏移的计算,需要再左移两位,得到 0xFFFFFF4
而且是有符号的,首位为1,前面都是 0xF 不用管,
看最后的 0x4 即 0b0100 (先忽略符号位1),现在是补码,等于反码 0b0011 加 1,则原码为 0b1100,
加上符号位,转换为十进制即 -12,也即 -0xC,
即当前PC地址要减去 0xC,正好就是汇编的 label 即 loc_8 的开头地址

由此可得:汇编的 label 其实就是基于 PC 偏移寻址。当然不同平台可能不一样,x86 里绝对寻址也是可行的。

一点没用的性能的分析:b 指令的偏移范围为 +/-128MB,跳转体最好不要写超过这个范围(虽然应该不会有人写这么大范围的),即 128 * 1024 * 1024 / 4 条指令数,超出则会增加指令辅助跳转。

NOTE

没必要过分妖魔化 goto,在本文的例子中,汇编指令与下面这种写法是一样的:

void fun(){
    while(1)
        printf("\n");
}

在 linux、cpython 等源码中都可以看到 goto 的身影。

另外提示一个用法:goto 不能跨越函数来写,只能跳转到自己函数内的 label。在 C 中可以使用 setjmp 和 longjmp 来跨函数跳转。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值