【问题标题】:Take address of value inside an interface获取接口内的值地址
【发布时间】:2013-03-09 06:34:55
【问题描述】:

如何获取接口内的值的地址?

我有一个存储在接口中的结构,在 list.List 元素中:

import "container/list"
type retry struct{}
p := &el.Value.(retry)

但我明白了:

cannot take the address of el.Value.(retry)

发生了什么事?既然结构体是存储在接口中的,为什么我得不到指向它的指针呢?

【问题讨论】:

    标签: pointers interface go memory-address


    【解决方案1】:

    要理解为什么这是不可能的,考虑一下接口变量实际上是什么是有帮助的。接口值占用两个字,第一个描述包含值的类型,第二个(a)保存包含的值(如果它适合该字)或(b)指向存储值的指针(如果值不适合单词)。

    需要注意的重要事项是 (1) 包含的值属于接口变量,以及 (2) 当为变量分配新值时,可以重用该值的存储。知道了,考虑下面的代码:

    var v interface{}
    v = int(42)
    p := GetPointerToInterfaceValue(&v) // a pointer to an integer holding 42
    v = &SomeStruct{...}
    

    现在整数的存储已被重新用于保存指针,*p 现在是该指针的整数表示。你可以看到它是如何破坏类型系统的,所以 Go 没有提供这样做的方法(除了使用 unsafe 包之外)。

    如果您需要一个指向要存储在列表中的结构的指针,那么一种选择是将指向该结构的指针存储在列表中,而不是直接存储结构值。或者,您可以传递 *list.Element 值作为对包含结构的引用。

    【讨论】:

    • 这似乎更合理。我认为可以说你不能保证包含的值的存储是足够的。
    • 转念一想,我没有看到任何情况下可以在不重新定位接口的情况下调整接口存储与接口的内联。
    • 好吧,我给出了一个用于接口值的存储被重用的案例。由于该语言不保证存储不会被重用,因此不给您一个指向该存储的类型化指针是有意义的。由于接口值在单独分配的内存中存储较大的值(使用第二个字作为指向该存储的指针),因此您将通过在列表中存储 *retry 值来使用相同数量的内存。
    【解决方案2】:

    在第一个近似值中:你不能这样做。即使可以,p 本身也必须具有 interface{} 类型并且不会有太大帮助 - 你不能直接取消引用它。

    强制性问题是:您要解决什么问题?

    最后但同样重要的是:接口定义行为而不是结构。直接使用接口的底层实现类型通常会破坏接口契约,尽管它可能存在非一般的合法情况。但是,对于一组有限的静态已知类型,type switch statement 已经提供了这些服务。

    【讨论】:

    • 我不明白为什么这是必要的。在类型断言之后,我应该能够获取应该相当于从接口返回的右值引用所指向的值的地址。
    • @MattJoiner:如果接口中包含的值不是指针类型,那么类型断言提供了一个副本。但是您的问题的标题是:“获取值的地址inside an interface”。 (强调我的)。 IOW,如果您想要一个指向放置在接口中的原始非指针类型的指针,类型断言将无济于事,因为接口内部仅携带一个指向此类实例的指针(适合机器字的模小值)。为什么不简单地在接口内放置一个指向值的指针?然后,您可以通过语言支持的方式直接获得您所追求的内容。
    【解决方案3】:

    类型断言是产生两个值的表达式。在这种情况下取​​地址是不明确的。

    p, ok := el.Value.(retry)
    if ok {
        // type assertion successful
        // now we can take the address
        q := &p 
    }
    

    来自cmets:

    请注意,这是指向值副本的指针,而不是指向值本身的指针。

    ——James Henstridge

    因此问题的解决方案很简单;在接口中存储一个指针,而不是一个值。

    【讨论】:

    • 请注意,这是指向值副本的指针,而不是指向值本身的指针。
    • 这是不合理的。当然,我在分配为自动变量p 后得到了一份副本。但是虽然它仍然是一个右值,但我应该能够获取值的地址,因为它存储在接口中。由于接口内联存储在Value 中,因此可以公平地假设接口内部 的值是堆分配的,因此可以就地修改。
    • @MattJoiner:接口包含的大值确实是堆分配的。但是指向该值的指针是未指定的、未公开的实现细节(我不推荐使用语言本身和模技术)。如果你需要一个来自接口的指针,那么将一个指针放入接口。
    【解决方案4】:

    Get pointer to interface value?

    有没有办法,给定一个接口类型的变量,得到一个 指向存储在变量中的值的指针?

    这是不可能的。

    罗伯·派克

    接口值不一定是可寻址的。例如,

    package main
    
    import "fmt"
    
    func main() {
        var i interface{}
        i = 42
        // cannot take the address of i.(int)
        j := &i.(int)
        fmt.Println(i, j)
    }
    

    Address operators

    对于 T 类型的操作数 x,地址操作 &x 生成一个 *T 类型的指针指向 x。操作数必须是可寻址的,即 变量、指针间接或切片索引操作; 或可寻址结构操作数的字段选择器;或数组 可寻址数组的索引操作。作为一个例外 可寻址性要求,x 也可以是复合文字。

    参考资料:

    Interface types

    Type assertions

    Go Data Structures: Interfaces

    Go Interfaces

    【讨论】:

      猜你喜欢
      • 2010-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-18
      • 2020-12-11
      • 2011-09-08
      相关资源
      最近更新 更多