Golang中函数会进行硬拷贝吗?

Golang中函数会进行硬拷贝吗? 如果我有一个名为 PrintBoard 的函数:

func PrintBoard(board [3][3]byte) {
    // prints out the board
}

Go 语言在打印前会创建数组的新副本吗?还是它会识别到不会发生任何修改,从而传递引用?我是否应该像下面这样定义函数?:

func PrintBoard(board *[3][3]byte) {
    // prints out the board
}
7 回复

请编写符合你意图的代码,只有在证明其成为性能瓶颈时才进行修改。

更多关于Golang中函数会进行硬拷贝吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


第一个版本复制了棋盘的切片头,但没有复制其内容。 因此,修改棋盘是可以的。 但是,如果你改变了棋盘的维度,那么这些更改将在函数退出时丢失。

数组是值类型,这意味着你无法在函数中更改其内容。 如果你使用切片或指向数组的指针,那是一个引用,你可以更改其内容。

但它会在内存中分配一个全新的数组吗?还是会传递一个只读引用?即使数组不会被修改,我是否应该传递指针?

我认为这取决于编译器优化——如果你按值传递一个数组,编译器可能会决定实际上通过指针来传递它(只要你在函数中确实不修改它)。但就像任何优化一样,这并不能保证一定会发生。

所有显式函数参数都是通过值传递的,因此每次调用 PrintBoard 时,你的 board 的9个字节都会被复制(至少在概念上)到 PrintBoard 中。话虽如此,这实际上取决于 PrintBoard 的复杂程度以及它如何被调用。例如,如果 PrintBoard 只是几个 fmt.Printf 或在写入某个 io.Writer 之前进行其他文本格式化,那么 PrintBoard 很可能被内联,并可以直接处理 board 的字节。然而,如果你这样做:

var boardValueProc func([3][3]byte)

func main() {
    doSomething()
    var board [3][3]byte
    boardValueProc(board)
    // ...
}

func doSomething() {
    // ...
    boardValueProc = PrintBoard
    // ...
}

那么编译器无法知道(在没有进行一些全程序分析的情况下)boardValueProc 中的函数不会修改棋盘中的字节,因此它总是会使用值的副本被调用。

自 Go 1.16 起,amd64 平台使用了一种新的基于寄存器的内部 ABI,因此那9个字节是通过寄存器传递的(具体是 CX 和 BX 寄存器;第9个字节被零扩展为完整的64位 BX 寄存器),所以实际的内存不再被复制,但值仍然被复制。

在 Go 语言中,数组是按值传递的,这意味着当你将数组传递给函数时,会发生硬拷贝(完整复制)。对于 [3][3]byte 这样的数组,会复制全部 9 个字节。

示例验证:

package main

import "fmt"

func modifyArray(arr [3][3]byte) {
    arr[0][0] = 'X' // 修改副本
}

func main() {
    board := [3][3]byte{
        {' ', ' ', ' '},
        {' ', ' ', ' '},
        {' ', ' ', ' '},
    }
    
    fmt.Printf("原始值: %c\n", board[0][0]) // 输出: ' '
    modifyArray(board)
    fmt.Printf("调用后: %c\n", board[0][0]) // 输出: ' '(未改变)
}

性能考虑: 对于小型数组(如 [3][3]byte),复制开销可以忽略。但如果是大型数组,建议使用指针或切片:

// 方案1:使用指针(避免复制)
func PrintBoardPtr(board *[3][3]byte) {
    fmt.Printf("%c", (*board)[0][0])
}

// 方案2:使用切片(引用底层数组)
func PrintBoardSlice(board [][]byte) {
    fmt.Printf("%c", board[0][0])
}

结论:

  • 当前 PrintBoard(board [3][3]byte) 会复制整个数组
  • 对于 [3][3]byte 这种小数组,无需优化
  • 如果数组很大或需要修改原数组,使用指针 *[N][M]byte 更合适
回到顶部