在Go中引用字符串字面量

28
在我的应用程序中,我经常会传递对静态字符串的引用。我希望避免每次调用时Go分配内存,但我无法获取到我的字符串文字的地址。
为什么无法获取字符串字面量的地址(请参见下面示例中的test1())?是我误解了语法,还是由于Go的内部工作原理而存在限制?
如果不可能,最好的解决方案是什么? test2() 可以工作,但它会每次分配内存给 var hej 吗?
test3() 不会分配任何新内存,但我希望避免函数外部的混乱。
package main

import "fmt"

var konnichiwa = `こんにちは世界`

// Gives the compile error `cannot take the address of "Hello world"`
func test1() (*string) { return &`Hello world` }

// Works fine
func test2() (*string) {
    hej := `Hej världen`
    return &hej
}

func test3() (*string) { return &konnichiwa }

func main(){
    fmt.Println(*test1())
    fmt.Println(*test2())
    fmt.Println(*test3())
}
感谢您的帮助!
5个回答

22
获取字面值(字符串、数字等)的地址是不合法的,因为它具有模糊的语义。
您是否要获取实际常量的地址?这将允许修改该值(可能导致运行时错误),还是您想分配一个新对象,复制常量并获取到新版本的地址?
test2的情况下,不存在这种歧义,因为您正在处理已经明确定义语义的现有变量。如果将字符串定义为const,则同样无法正常工作。
语言规范通过明确不允许您所要求的操作来避免此歧义。解决方案是使用test2。虽然略微冗长,但它使规则简单明了。
当然,每个规则都有其例外情况,在Go中涉及组合文字:以下内容是合法且在规范中定义的:
func f() interface{} {
    return &struct {
        A int
        B int
    }{1, 2} 
}

19

针对在你的情况下传递“静态”字符串的最佳解决方案问题:

  1. 传递字符串类型而不是 *string。
  2. 不要假设底层正在发生什么。

给出“不必担心分配字符串”的建议很诱人,因为在你所描述的情况下,同一个字符串可能会被多次传递。然而,在一般情况下,考虑内存使用是非常重要的。猜测内存使用是非常糟糕的,基于对其他语言的经验来猜测更是如此。

这是你程序的修改版本。你猜想内存是在哪里分配的?

package main

import "fmt"

var konnichiwa = `こんにちは世界`

func test1() *string {
    s := `Hello world`
    return &s
}

func test2() string {
    return `Hej världen`
}

func test3() string {
    return konnichiwa
}

func main() {
    fmt.Println(*test1())
    fmt.Println(test2())
    fmt.Println(test3())
}

现在请编译器来帮忙:

> go tool 6g -S t.go

(我把程序命名为t.go.) 在输出结果中搜索对runtime.new的调用。只有一个!我会告诉你,它在test1中。

因此,不要离题太远,编译器输出的小片段表明,我们通过使用string类型而不是*string类型来避免了分配。


7
不仅我对Go何时决定分配新内存有了更好的理解,而且(或更糟!)您还教会了我如何使用“go tool”自己获取汇编代码。该死,现在我将花费数小时来分析Go的汇编代码以获得乐趣。谢谢!(是的,我会坚持使用您的解决方案) - ANisus
我在使用Go 1.10时遇到了错误go tool: no such tool "6g",但是这个命令可以正常工作:go tool compile -S t.go - psmith

7

在Go语言中,字符串是不可变的,只是一个指针和长度(总长度为2个字)。

因此,您不必使用指针来有效处理它。

只需传递字符串即可。


一个结构体需要一个可选属性,因为空字符串是有效值吗? - dtoux
11
由于某些API需要传递一个字符串指针,因此这并不是有帮助的。 - Keith Ripley

5
  1. 在Go语言中,传递字符串通常不会分配内存 - 它是一个值类型(指向字节的指针和一个长度int)。

  2. 仅支持对复合字面量取地址,例如:

    v := &T{1, "foo"}

    但对于简单值,如

    w := &1

    x := &"foo"


0
我正在使用interface{}来表示我的字符串有时可能为空。在我的情况下,它看起来像这样:
testCases := []struct {
        Values map[string]interface{}
    }{
        {
            Values: map[string]interface{}{"var1": nil},
        },
        {
            Values: map[string]interface{}{"var1": "my_cool_string"},
        },
    }

接下来在以下代码中(您可能还想进行断言检查):

if v != nil {
    vv := v.(string)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接