Slice使用的一个问题

buaatianwanli · 2016-03-04 03:51:32 · 1909 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2016-03-04 03:51:32 的主题,其中的信息可能已经有所发展或是发生改变。

package main

import (
    "fmt"
    "sync"
)

type Data struct {
    A []int32
    B []string
}

func main() {
    channel := make(chan Data)
    data := Data{A: make([]int32, 0, 1), B: make([]string, 0, 1)}

    fmt.Printf("Before gorountine addr, A: %p\n", data.A)
    fmt.Printf("Before gorountine addr, B: %p\n", data.B)
    wg := new(sync.WaitGroup)
    wg.Add(1)
    go func() {
        for d := range channel {
            fmt.Printf("goroutine addr before append, A: %p\n", d.A)
            fmt.Printf("goroutine addr before append, B: %p\n", d.B)
            d.A = append(d.A, 23)
            d.B = append(d.B, "23")
            //d.A[0] = 23
            //d.B[0] = "23"
            fmt.Printf("goroutine addr after append, A: %p\n", d.A)
            fmt.Printf("goroutine addr after append, B: %p\n", d.B)
            fmt.Println(d)
        }
        wg.Done()
    }()

    fmt.Printf("Before: %v\n", data)
    channel <- data
    close(channel)
    wg.Wait()
    fmt.Printf("After gorountine addr, A: %p\n", data.A)
    fmt.Printf("After gorountine addr, B: %p\n", data.B)
    fmt.Printf("After: %v\n", data)
}

代码见上

输出见下:

Before gorountine addr, A: 0xc20800a1dc
Before gorountine addr, B: 0xc20800a200
Before: {[] []}
goroutine addr before append, A: 0xc20800a1dc
goroutine addr before append, B: 0xc20800a200
goroutine addr after append, A: 0xc20800a1dc
goroutine addr after append, B: 0xc20800a200
{[23] [23]}
After gorountine addr, A: 0xc20800a1dc
After gorountine addr, B: 0xc20800a200
After: {[] []}

我的问题是:为什么slice的地址没有变,但是master goroutine里的data数据为空?

如果我去掉append,改为直接赋值,最后data则不为空

需要注意的是,我在make slice的时候特地设置cap为1


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1909 次点击  ∙  2 赞  
加入收藏 微博
18 回复  |  直到 2016-03-07 09:06:17
buaatianwanli
buaatianwanli · #1 · 9年之前

各位大拿给出宝贵意见

hua666777
hua666777 · #2 · 9年之前

看起来好难1

buaatianwanli
buaatianwanli · #3 · 9年之前

golang里的routine有没有自己的地址空间? 从第二段代码来看,应该是没有。

但是第一段代码怎么解释呢?

txgo
txgo · #4 · 9年之前

初学者,楼主如果是为了要修改data值,那么channel应该定义为*Data,运行后的结果就是会被修改。

    channel := make(chan *Data)
    data := &Data{A: make([]int32, 0, 1), B: make([]string, 0, 1)}

至于为什么地址没有变的问题,楼主我们你是故意的么,%p,打印的是传参也是传值得啊,你每次都copy当然都是一样的啊。

请看下面的代码:

    var s []int
fmt.Printf("\t  %v - %p - %p\n", s, s, &s)
s = make([]int, 0, 1)
fmt.Printf("\t  %v - %p - %p\n", s, s, &s)
s = append(s, 1)
fmt.Printf("\t  %v - %p - %p\n", s, s, &s)
copy := s
fmt.Printf("\t  %v - %p - %p\n", copy, copy, ©)

输出结果为:

       [] - 0x0 - 0xc08200a740
      [] - 0xc082002370 - 0xc08200a740
      [1] - 0xc082002370 - 0xc08200a740
      [1] - 0xc082002370 - 0xc08200a820

s定义的是时候是nil,所以%p打印的时候的是nil,但是地址是有的,这个地址在用make初始化之后不变,但是值已经变了,不再是nil了,但是在append值之后,也是不会变的,但是地址变了啊。

最后一行,如果做一个copy,值仍然是相等的啊,所以不变,但是呢地址已经变了啊。

所以回到你的问题,你要定位问题,请把传递给printf的参数%p的变量加个&。

buaatianwanli
buaatianwanli · #5 · 9年之前
txgotxgo #4 回复

初学者,楼主如果是为了要修改data值,那么channel应该定义为*Data,运行后的结果就是会被修改。 ``` channel := make(chan *Data) data := &Data{A: make([]int32, 0, 1), B: make([]string, 0, 1)} ``` 至于为什么地址没有变的问题,楼主我们你是故意的么,%p,打印的是传参也是传值得啊,你每次都copy当然都是一样的啊。 请看下面的代码: var s []int fmt.Printf("\t %v - %p - %p\n", s, s, &s) s = make([]int, 0, 1) fmt.Printf("\t %v - %p - %p\n", s, s, &s) s = append(s, 1) fmt.Printf("\t %v - %p - %p\n", s, s, &s) copy := s fmt.Printf("\t %v - %p - %p\n", copy, copy, ©) 输出结果为: [] - 0x0 - 0xc08200a740 [] - 0xc082002370 - 0xc08200a740 [1] - 0xc082002370 - 0xc08200a740 [1] - 0xc082002370 - 0xc08200a820 s定义的是时候是nil,所以%p打印的时候的是nil,但是地址是有的,这个地址在用make初始化之后不变,但是值已经变了,不再是nil了,但是在append值之后,也是不会变的,但是地址变了啊。 最后一行,如果做一个copy,值仍然是相等的啊,所以不变,但是呢地址已经变了啊。 所以回到你的问题,你要定位问题,请把传递给printf的参数%p的变量加个&。

就是代码学习,所以不考虑别的方法绕过去。

我使用%p就是想打印地址,地址相同说明在append的时候没有重新分配空间(如非goroutine的进程有独立的地址空间)

buaatianwanli
buaatianwanli · #6 · 9年之前
txgotxgo #4 回复

初学者,楼主如果是为了要修改data值,那么channel应该定义为*Data,运行后的结果就是会被修改。 ``` channel := make(chan *Data) data := &Data{A: make([]int32, 0, 1), B: make([]string, 0, 1)} ``` 至于为什么地址没有变的问题,楼主我们你是故意的么,%p,打印的是传参也是传值得啊,你每次都copy当然都是一样的啊。 请看下面的代码: var s []int fmt.Printf("\t %v - %p - %p\n", s, s, &s) s = make([]int, 0, 1) fmt.Printf("\t %v - %p - %p\n", s, s, &s) s = append(s, 1) fmt.Printf("\t %v - %p - %p\n", s, s, &s) copy := s fmt.Printf("\t %v - %p - %p\n", copy, copy, ©) 输出结果为: [] - 0x0 - 0xc08200a740 [] - 0xc082002370 - 0xc08200a740 [1] - 0xc082002370 - 0xc08200a740 [1] - 0xc082002370 - 0xc08200a820 s定义的是时候是nil,所以%p打印的时候的是nil,但是地址是有的,这个地址在用make初始化之后不变,但是值已经变了,不再是nil了,但是在append值之后,也是不会变的,但是地址变了啊。 最后一行,如果做一个copy,值仍然是相等的啊,所以不变,但是呢地址已经变了啊。 所以回到你的问题,你要定位问题,请把传递给printf的参数%p的变量加个&。

另外,你仔细看我的代码。 我在给slice初始化的时候是设置cap为1的,所以不存在你说的地址变了的问题。 另外我答应地址的结果也证明了地址没有变化。

txgo
txgo · #7 · 9年之前

@buaatianwanli 可能是我没有说清楚,我的意思是你的打印了s的值,这个值是不变的,而实际上要打印的是s的地址,这个地址在复制的时候是变化的。你的代码使用channel传递的是值,在go routine对这个复制的值进行了修改,而在main routine中原来的值当然不受影响啦。

buaatianwanli
buaatianwanli · #8 · 9年之前
txgotxgo #7 回复

@buaatianwanli 可能是我没有说清楚,我的意思是你的打印了s的值,这个值是不变的,而实际上要打印的是s的地址,这个地址在复制的时候是变化的。你的代码使用channel传递的是值,在go routine对这个复制的值进行了修改,而在main routine中原来的值当然不受影响啦。

我明白你的意思。struct是拷贝的,但是struct里面是slice,是不是拷贝之后的那个struct中的slice指向的还是原来的struct中slice指向的内存地址呢?如果是,我打印任何一个struct都应该是相同的值,对吗?

txgo
txgo · #9 · 9年之前

同学,我还有一个意思是你的printf打印地址用错了!打印值v的地址要用&v,而不是v。所以你打印出来都是一样的,而且我用了一段代码来演示打印地址的时候用v和&v的区别。

buaatianwanli
buaatianwanli · #10 · 9年之前
txgotxgo #9 回复

同学,我还有一个意思是你的printf打印地址用错了!打印值v的地址要用&v,而不是v。所以你打印出来都是一样的,而且我用了一段代码来演示打印地址的时候用v和&v的区别。

地址好像是打错了。。。。你重新看看这个代码。

buaatianwanli
buaatianwanli · #11 · 9年之前

package main

import (
    "fmt"
    "sync"
)

type Data struct {
    A []int32
    B []string
}

func main() {
    channel := make(chan Data)
    data := Data{A: make([]int32, 1, 1), B: make([]string, 1, 1)}

    fmt.Printf("Before gorountine addr, A: %p\n", &data.A)
    fmt.Printf("Before gorountine addr, B: %p\n", &data.B)
    wg := new(sync.WaitGroup)
    wg.Add(1)
    go func() {
        for d := range channel {
            fmt.Printf("goroutine addr before append, A: %p\n", &d.A)
            fmt.Printf("goroutine addr before append, B: %p\n", &d.B)
            //d.A = append(d.A, 23)
            //d.B = append(d.B, "23")
            d.A[0] = 23
            d.B[0] = "23"
            fmt.Printf("goroutine addr after append, A: %p\n", &d.A)
            fmt.Printf("goroutine addr after append, B: %p\n", &d.B)
            fmt.Println(d)
        }
        }
        wg.Done()
    }()

    fmt.Printf("Before: %+v\n", data)
    channel <- data
    close(channel)
    wg.Wait()
    fmt.Printf("After gorountine addr, A: %p\n", &data.A)
    fmt.Printf("After gorountine addr, B: %p\n", &data.B)
    fmt.Printf("After: %+v\n", data)
}
buaatianwanli
buaatianwanli · #12 · 9年之前

结果:

Before gorountine addr, A: 0xc82000e210
Before gorountine addr, B: 0xc82000e228
Before: {A:[0] B:[]}
goroutine addr before append, A: 0xc82000e270
goroutine addr before append, B: 0xc82000e288
goroutine addr after append, A: 0xc82000e270
goroutine addr after append, B: 0xc82000e288
{[23] [23]}
After gorountine addr, A: 0xc82000e210
After gorountine addr, B: 0xc82000e228
After: {A:[23] B:[23]}
txgo
txgo · #13 · 9年之前

我明白了,这个问题不是struct的值对应的slice是否变化的问题,本质是slice的底层数组表示。

你可能也注意到了,如果使用s = append(s, 1)和s[0]=1结果不太一样,对吧?

请看这段代码:

package main

import (
    "fmt"
)

func main() {
    var s []int
    fmt.Printf("\t  %v - %p - %p\n", s, s, &s)

    s = make([]int, 0, 1)
    fmt.Printf("\t  %v - %p - %p\n", s, s, &s)

    s = append(s, 1)
    fmt.Printf("\t  %v - %p - %p\n", s, s, &s)

    copy := s
    fmt.Printf("\t  %v - %p - %p\n", copy, copy, ©)

    copy[0] = 2
    fmt.Printf("\t  %v - %p - %p\n", copy, copy, ©)
    fmt.Printf("\t  %v - %p - %p\n", s, s, &s)
}

运行结果是:

      [] - 0x0 - 0xc820056080
      [] - 0xc82005c1c0 - 0xc820056080
      [1] - 0xc82005c1c0 - 0xc820056080
      [1] - 0xc82005c1c0 - 0xc820056180
      [2] - 0xc82005c1c0 - 0xc820056180
      [2] - 0xc82005c1c0 - 0xc820056080

也就是被copy变量和原变量是共用了底层数组,所以修改copy之后,原来的slice也会被修改(直到超出容量之后重新分配底层数组)。

buaatianwanli
buaatianwanli · #14 · 9年之前
txgotxgo #13 回复

我明白了,这个问题不是struct的值对应的slice是否变化的问题,本质是slice的底层数组表示。 你可能也注意到了,如果使用s = append(s, 1)和s[0]=1结果不太一样,对吧? 请看这段代码: ``` package main import ( "fmt" ) func main() { var s []int fmt.Printf("\t %v - %p - %p\n", s, s, &s) s = make([]int, 0, 1) fmt.Printf("\t %v - %p - %p\n", s, s, &s) s = append(s, 1) fmt.Printf("\t %v - %p - %p\n", s, s, &s) copy := s fmt.Printf("\t %v - %p - %p\n", copy, copy, ©) copy[0] = 2 fmt.Printf("\t %v - %p - %p\n", copy, copy, ©) fmt.Printf("\t %v - %p - %p\n", s, s, &s) } ``` 运行结果是: ``` [] - 0x0 - 0xc820056080 [] - 0xc82005c1c0 - 0xc820056080 [1] - 0xc82005c1c0 - 0xc820056080 [1] - 0xc82005c1c0 - 0xc820056180 [2] - 0xc82005c1c0 - 0xc820056180 [2] - 0xc82005c1c0 - 0xc820056080 ``` 也就是被copy变量和原变量是共用了底层数组,所以修改copy之后,原来的slice也会被修改(直到超出容量之后重新分配底层数组)。

对,我主要就是想讨论这个问题。 我在使用append的时候是这样初始化slice的:s := make([]int32, 0, 1) 这样,初始化一个空的slice,但是它的cap是1. 当我append的时候,虽然s的len(s)是1,但是append的时候却不会重新分配底层的数组。

另外一个问题就是,slice本身是由headSlice这个内部结构实现的。按理说说这样的操作: copy := s之后,&copy不应该和&s一样吧?

buaatianwanli
buaatianwanli · #15 · 9年之前
buaatianwanlibuaatianwanli #14 回复

#13楼 @txgo 对,我主要就是想讨论这个问题。 我在使用append的时候是这样初始化slice的:s := make([]int32, 0, 1) 这样,初始化一个空的slice,但是它的cap是1. 当我append的时候,虽然s的len(s)是1,但是append的时候却不会重新分配底层的数组。 另外一个问题就是,slice本身是由headSlice这个内部结构实现的。按理说说这样的操作: copy := s之后,©不应该和&s一样吧?

copy := s看错了

elvingo
elvingo · #16 · 9年之前

<iframe style="border:1px solid" src="https://wide.b3log.org/playground/b593d236d08d0463a4c1a7e1589f36aa.go?embed=true&#34; width="99%" height="600"></iframe>

elvingo
elvingo · #17 · 9年之前
elvingoelvingo #16 回复

Before gorountine addr, A: 0xc20800a1dc(数组的首地址)-0xc20803a180(数组对象的地址) Before gorountine addr, B: 0xc20800a200(数组的首地址)-0xc20803a198(数组对象的地址) Before: {[] []} goroutine addr before append, A: 0xc20800a1dc(数组的首地址)-0xc20803a1e0(数组对象的地址),cap:1 goroutine addr before append, B: 0xc20800a200(数组的首地址)-0xc20803a1f8(数组对象的地址),cap:1 goroutine addr after append, A: 0xc20800a280(数组的首地址)-0xc20803a1e0(数组对象的地址),cap:2 goroutine addr after append, B: 0xc20801e100(数组的首地址)-0xc20803a1f8(数组对象的地址),cap:2 {[0 1] [0 1]} After gorountine addr, A: 0xc20800a1dc(数组的首地址)-0xc20803a180(数组对象的地址) After gorountine addr, B: 0xc20800a200(数组的首地址)-0xc20803a198(数组对象的地址) After: {[] []}

buaatianwanli
buaatianwanli · #18 · 9年之前
elvingoelvingo #17 回复

#16楼 @elvingo Before gorountine addr, A: 0xc20800a1dc(数组的首地址)-0xc20803a180(数组对象的地址) Before gorountine addr, B: 0xc20800a200(数组的首地址)-0xc20803a198(数组对象的地址) Before: {[] []} goroutine addr before append, A: 0xc20800a1dc(数组的首地址)-0xc20803a1e0(数组对象的地址),cap:1 goroutine addr before append, B: 0xc20800a200(数组的首地址)-0xc20803a1f8(数组对象的地址),cap:1 goroutine addr after append, A: 0xc20800a280(数组的首地址)-0xc20803a1e0(数组对象的地址),cap:2 goroutine addr after append, B: 0xc20801e100(数组的首地址)-0xc20803a1f8(数组对象的地址),cap:2 {[0 1] [0 1]} After gorountine addr, A: 0xc20800a1dc(数组的首地址)-0xc20803a180(数组对象的地址) After gorountine addr, B: 0xc20800a200(数组的首地址)-0xc20803a198(数组对象的地址) After: {[] []}

append两次地址当然变了。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传