>  기사  >  백엔드 개발  >  For-Range는 golang의 어셈블리 코드와 어떻게 작동합니까?

For-Range는 golang의 어셈블리 코드와 어떻게 작동합니까?

王林
王林앞으로
2024-02-09 17:18:09983검색

For-Range 如何在 golang 中与汇编代码一起工作?

php小编草莓为您介绍一种在golang中与汇编代码一起工作的方法——For-Range。For-Range是golang中的一个循环结构,可以与汇编代码结合使用,提供更高效的性能和灵活性。通过使用For-Range,您可以在golang中轻松处理大量的数据,并且可以借助汇编代码的优势,提升程序的执行效率。在本文中,我们将详细介绍For-Range的使用方法,并讲解如何与汇编代码进行协作,以实现更高效的程序运行。

问题内容

当源代码被汇编时,我对 golang 中 for-range 内的指针用法感到困惑。例如,我们知道下面的变量value将始终位于相同的内存地址中,并且相应的汇编代码显示了相同的逻辑。

// Source Code
func main() {
a := []int{1, 3, 5}

    for _, value := range a {
        foo(&value)
    }

}

func foo(a *int) int {
b := *a * 42
fmt.Println(b)
return b
}

// Assembly Code
"".main STEXT size=126 args=0x0 locals=0x38 funcid=0x0
0x0000 00000 (main.go:15)       TEXT    "".main(SB), ABIInternal, $56-0
0x0000 00000 (main.go:15)       CMPQ    SP, 16(R14)
0x0004 00004 (main.go:15)       PCDATA  $0, $-2
0x0004 00004 (main.go:15)       JLS     119
0x0006 00006 (main.go:15)       PCDATA  $0, $-1
0x0006 00006 (main.go:15)       SUBQ    $56, SP
0x000a 00010 (main.go:15)       MOVQ    BP, 48(SP)
0x000f 00015 (main.go:15)       LEAQ    48(SP), BP
0x0014 00020 (main.go:15)       FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0014 00020 (main.go:15)       FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0014 00020 (main.go:16)       MOVQ    $0, ""..autotmp_4+24(SP)
0x001d 00029 (main.go:16)       LEAQ    ""..autotmp_4+32(SP), CX
0x0022 00034 (main.go:16)       MOVUPS  X15, (CX)
0x0026 00038 (main.go:16)       MOVQ    $1, ""..autotmp_4+24(SP)
0x002f 00047 (main.go:16)       MOVQ    $3, ""..autotmp_4+32(SP)
0x0038 00056 (main.go:16)       MOVQ    $5, ""..autotmp_4+40(SP)
0x0041 00065 (main.go:16)       XORL    AX, AX
0x0043 00067 (main.go:18)       JMP     103
0x0045 00069 (main.go:18)       MOVQ    AX, ""..autotmp_10+16(SP)
0x004a 00074 (main.go:18)       MOVQ    ""..autotmp_4+24(SP)(AX\*8), CX
0x004f 00079 (main.go:18)       MOVQ    CX, "".value+8(SP)
0x0054 00084 (main.go:19)       LEAQ    "".value+8(SP), AX        // Here we see we always use "".value+8(SP) as the argument into foo()
0x0059 00089 (main.go:19)       PCDATA  $1, $0
0x0059 00089 (main.go:19)       CALL    "".foo(SB)
0x005e 00094 (main.go:18)       MOVQ    ""..autotmp_10+16(SP), CX
0x0063 00099 (main.go:18)       LEAQ    1(CX), AX
0x0067 00103 (main.go:18)       CMPQ    AX, $3
0x006b 00107 (main.go:18)       JLT     69
0x006d 00109 (main.go:21)       PCDATA  $1, $-1
0x006d 00109 (main.go:21)       MOVQ    48(SP), BP
0x0072 00114 (main.go:21)       ADDQ    $56, SP
0x0076 00118 (main.go:21)       RET
0x0077 00119 (main.go:21)       NOP
0x0077 00119 (main.go:15)       PCDATA  $1, $-1
0x0077 00119 (main.go:15)       PCDATA  $0, $-2
0x0077 00119 (main.go:15)       CALL    runtime.morestack_noctxt(SB)
0x007c 00124 (main.go:15)       PCDATA  $0, $-1
0x007c 00124 (main.go:15)       JMP     0
0x0000 49 3b 66 10 76 71 48 83 ec 38 48 89 6c 24 30 48  I;f.vqH..8H.l$0H
0x0010 8d 6c 24 30 48 c7 44 24 18 00 00 00 00 48 8d 4c  .l$0H.D$.....H.L
0x0020 24 20 44 0f 11 39 48 c7 44 24 18 01 00 00 00 48  $ D..9H.D$.....H
0x0030 c7 44 24 20 03 00 00 00 48 c7 44 24 28 05 00 00  .D$ ....H.D$(...
0x0040 00 31 c0 eb 22 48 89 44 24 10 48 8b 4c c4 18 48  .1.."H.D$.H.L..H
0x0050 89 4c 24 08 48 8d 44 24 08 e8 00 00 00 00 48 8b  .L$.H.D$......H.
0x0060 4c 24 10 48 8d 41 01 48 83 f8 03 7c d8 48 8b 6c  L$.H.A.H...|.H.l
0x0070 24 30 48 83 c4 38 c3 e8 00 00 00 00 eb 82        $0H..8........
rel 90+4 t=7 "".foo+0
rel 120+4 t=7 runtime.morestack_noctxt+0

但是,当我更改源代码并尝试查看汇编代码中的变化时,我发现没有任何变化。

// Changed Source Code
func main() {
a := []int{1, 3, 5}

    for _, value := range a {
        v := value
        foo(&v)
    }

}

func foo(a *int) int {
b := *a * 42
fmt.Println(b)
return b
}

// Changed Assembly Code
"".main STEXT size=126 args=0x0 locals=0x38 funcid=0x0
0x0000 00000 (main.go:15)       TEXT    "".main(SB), ABIInternal, $56-0
0x0000 00000 (main.go:15)       CMPQ    SP, 16(R14)
0x0004 00004 (main.go:15)       PCDATA  $0, $-2
0x0004 00004 (main.go:15)       JLS     119
0x0006 00006 (main.go:15)       PCDATA  $0, $-1
0x0006 00006 (main.go:15)       SUBQ    $56, SP
0x000a 00010 (main.go:15)       MOVQ    BP, 48(SP)
0x000f 00015 (main.go:15)       LEAQ    48(SP), BP
0x0014 00020 (main.go:15)       FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0014 00020 (main.go:15)       FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0014 00020 (main.go:16)       MOVQ    $0, ""..autotmp_5+24(SP)
0x001d 00029 (main.go:16)       LEAQ    ""..autotmp_5+32(SP), CX
0x0022 00034 (main.go:16)       MOVUPS  X15, (CX)
0x0026 00038 (main.go:16)       MOVQ    $1, ""..autotmp_5+24(SP)
0x002f 00047 (main.go:16)       MOVQ    $3, ""..autotmp_5+32(SP)
0x0038 00056 (main.go:16)       MOVQ    $5, ""..autotmp_5+40(SP)
0x0041 00065 (main.go:16)       XORL    AX, AX
0x0043 00067 (main.go:18)       JMP     103
0x0045 00069 (main.go:18)       MOVQ    AX, ""..autotmp_11+16(SP)
0x004a 00074 (main.go:18)       MOVQ    ""..autotmp_5+24(SP)(AX\*8), CX
0x004f 00079 (main.go:19)       MOVQ    CX, "".v+8(SP)
0x0054 00084 (main.go:20)       LEAQ    "".v+8(SP), AX    // Here we see the logic of argument is the same as above. This makes me confused.
0x0059 00089 (main.go:20)       PCDATA  $1, $0
0x0059 00089 (main.go:20)       CALL    "".foo(SB)
0x005e 00094 (main.go:18)       MOVQ    ""..autotmp_11+16(SP), CX
0x0063 00099 (main.go:18)       LEAQ    1(CX), AX
0x0067 00103 (main.go:18)       CMPQ    AX, $3
0x006b 00107 (main.go:18)       JLT     69
0x006d 00109 (main.go:22)       PCDATA  $1, $-1
0x006d 00109 (main.go:22)       MOVQ    48(SP), BP
0x0072 00114 (main.go:22)       ADDQ    $56, SP
0x0076 00118 (main.go:22)       RET
0x0077 00119 (main.go:22)       NOP
0x0077 00119 (main.go:15)       PCDATA  $1, $-1
0x0077 00119 (main.go:15)       PCDATA  $0, $-2
0x0077 00119 (main.go:15)       CALL    runtime.morestack_noctxt(SB)
0x007c 00124 (main.go:15)       PCDATA  $0, $-1
0x007c 00124 (main.go:15)       JMP     0
0x0000 49 3b 66 10 76 71 48 83 ec 38 48 89 6c 24 30 48  I;f.vqH..8H.l$0H
0x0010 8d 6c 24 30 48 c7 44 24 18 00 00 00 00 48 8d 4c  .l$0H.D$.....H.L
0x0020 24 20 44 0f 11 39 48 c7 44 24 18 01 00 00 00 48  $ D..9H.D$.....H
0x0030 c7 44 24 20 03 00 00 00 48 c7 44 24 28 05 00 00  .D$ ....H.D$(...
0x0040 00 31 c0 eb 22 48 89 44 24 10 48 8b 4c c4 18 48  .1.."H.D$.H.L..H
0x0050 89 4c 24 08 48 8d 44 24 08 e8 00 00 00 00 48 8b  .L$.H.D$......H.
0x0060 4c 24 10 48 8d 41 01 48 83 f8 03 7c d8 48 8b 6c  L$.H.A.H...|.H.l
0x0070 24 30 48 83 c4 38 c3 e8 00 00 00 00 eb 82        $0H..8........
rel 90+4 t=7 "".foo+0
rel 120+4 t=7 runtime.morestack_noctxt+0

那么局部变量 v 如何影响 for-range 呢?

正如上面的细节,我认为汇编代码应该显示新的局部变量的引入是如何工作的,但事实并非如此。

解决方法

几点。

规范仅针对您的情况说明了以下内容:

这就是全部:它说将会有变量,并且它们将会 重新使用。后一点意味着,比如说,如果您创建一个闭包(使用函数文字的匿名函数),它将关闭一个或多个迭代变量,并将其返回/保存在某处并在循环结束后调用或者与循环同时(例如,在一个单独的 goroutine 中),该闭包将在循环的每次迭代更新(或正在更新)时访问这些完全相同的变量。

如果您不做任何此类奇特的事情 - 例如,仅从循环体代码中的那些变量中读取,那么这些变量被重用的事实是无关紧要的。

让我们重申一下:规范没有对迭代变量的内存地址提供任何保证。

为什么这很重要?因为编译器可以自由地生成它希望的任何代码,只要结果以遵循规范的方式工作,并且编译器的作用至少取决于以下内容:

  • 目标硬件(GOARCH)。

  • Go 的版本和 make(实现)。不要低估这一点:例如,规范并没有精确定义 GC 的工作方式,因此任何品牌和任何版本的 Go 都可以自由地实现移动 GC,这将移动内存中的任意变量并更新指向的所有指针他们。
    目前,流行的 Go 版本(您应该使用的版本和 GCC 前端)并没有这样做,但没有什么可以阻止它们或任何其他实现这样做。

因此,最后,您会问为什么特定的编译器会生成看起来特别的代码,并且既不说明 Go 的品牌,也不说明其版本,也不说明您的 GOARCH (尽管可以猜测它可能是 amd64) 。因此,您的问题实际上是无法回答的,并且精确的答案不会太有用,因为它们很快就会过时。
因此,此类问题与 SO 无关。 我决定将所有这些作为答案,只是因为对于评论来说太多了。

在您的特定情况下,编译器可能分析了 foo 的代码,并发现它不会更新通过指针参数传递给它的变量,并且更重要的是不会将其进一步传递到调用堆栈。由于循环体中的 v 没有做任何其他事情,编译器可能会认为语义 v 可以被视为 variable 的纯粹别名,并且它只是跳过了该单独变量的创建。我可以想象,如果您将这些变量的指针传递给 foo,并且让它打印这些地址,编译器将被迫实现 v

请注意,“关于”只是一个猜测。如果您想了解所有细节,您可以随时研究编译器的工作原理(Go 的两种流行实现都是 F/OSS 的一部分)和/或检测其代码。

另请注意,您可以要求编译器告诉您有关其功能的更多信息。最常用的 Go 实现(最初称为 gc )在其 go buildgo install 调用中支持 -gcflags 命令行参数,该调用将参数传递给编译器(请参阅 go 工具编译 -help )。特别是,它的 -m-N (还有 -S-live)标志可能值得玩一下。

위 내용은 For-Range는 golang의 어셈블리 코드와 어떻게 작동합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 stackoverflow.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제