为什么 `*(*string)(unsafe.Pointer(&b))` 在 `bufio.Reader` 中不起作用?

huangapple go评论76阅读模式
英文:

why *(*string)(unsafe.Pointer(&b)) doesn't work with bufio.Reader

问题

我有一个文件,里面包含一些 IP 地址。我使用以下代码读取文件并进行切片,然后使用*(*string)(unsafe.Pointer(&b))[]byte解析为字符串,但是它不起作用。

func TestInitIpRangeFromFile(t *testing.T) {
   filepath := "/tmp/test"
   file, err := os.Open(filepath)
   if err != nil {
      t.Errorf("failed to open ip range file:%s, err:%s", filepath, err)
   }
   reader := bufio.NewReader(file)
   ranges := make([]string, 0)
   for {
      ip, _, err := reader.ReadLine()
      if err != nil {
         if err == io.EOF {
            break
         }
         logger.Fatalf("failed to read ip range file, err:%s", err)
      }
      t.Logf("ip:%s", *(*string)(unsafe.Pointer(&ip)))
      ranges = append(ranges, *(*string)(unsafe.Pointer(&ip)))
   }

   t.Logf("%v", ranges)
}

结果:

    task_test.go:71: ip:1.1.1.0/24
    task_test.go:71: ip:1.1.2.0/24
    task_test.go:71: ip:2.2.1.0/24
    task_test.go:71: ip:2.2.2.0/24
    task_test.go:75: [2.2.2.0/24 1.1.2.0/24 2.2.1.0/24 2.2.2.0/24]

为什么1.1.1.0/24变成了2.2.2.0/24

*(*string)(unsafe.Pointer(&ip))改为string(ip)就可以正常工作了。

英文:

i have a file. it has some ip

1.1.1.0/24
1.1.2.0/24
2.2.1.0/24
2.2.2.0/24

i read this file to slice, and used *(*string)(unsafe.Pointer(&b)) to parse []byte to string, but is doesn't work

func TestInitIpRangeFromFile(t *testing.T) {
   filepath := "/tmp/test"
   file, err := os.Open(filepath)
   if err != nil {
      t.Errorf("failed to open ip range file:%s, err:%s", filepath, err)
   }
   reader := bufio.NewReader(file)
   ranges := make([]string, 0)
   for {
      ip, _, err := reader.ReadLine()
      if err != nil {
         if err == io.EOF {
            break
         }
         logger.Fatalf("failed to read ip range file, err:%s", err)
      }
      t.Logf("ip:%s", *(*string)(unsafe.Pointer(&ip)))
      ranges = append(ranges, *(*string)(unsafe.Pointer(&ip)))
   }

   t.Logf("%v", ranges)
}

result:

    task_test.go:71: ip:1.1.1.0/24
    task_test.go:71: ip:1.1.2.0/24
    task_test.go:71: ip:2.2.1.0/24
    task_test.go:71: ip:2.2.2.0/24
    task_test.go:75: [2.2.2.0/24 1.1.2.0/24 2.2.1.0/24 2.2.2.0/24]

why 1.1.1.0/24 changed to 2.2.2.0/24 ?

change

*(*string)(unsafe.Pointer(&ip))

to string(ip) it works

答案1

得分: 2

所以,将一个切片头部重新解释为字符串头部的方式是完全疯狂的,没有任何保证能正确工作,它只是间接导致了你的问题。

真正的问题是你保留了对 bufio/Reader.ReadLine() 返回值的指针,但是该方法的文档中说:“返回的缓冲区只在下一次调用 ReadLine 之前有效。”这意味着读取器可以在以后重用该内存,而这就是发生的情况。

当你以正确的方式进行类型转换,string(ip),Go 会将缓冲区的内容复制到新创建的字符串中,该字符串在未来仍然有效。但是当你将切片强制转换为字符串时,你保留了完全相同的指针,该指针在读取器重新填充其缓冲区后停止工作。

如果你决定使用指针技巧作为性能优化来避免复制和分配... 那就太糟糕了。读取器接口将强制你将数据复制出来,既然如此,你应该直接使用 string()

英文:

So, while reinterpreting a slice-header as a string-header the way you did is absolutely bonkers and has no guarantee whatsoever of working correctly, it's only indirectly the cause of your problem.

The real problem is that you're retaining a pointer to the return value of bufio/Reader.ReadLine(), but the docs for that method say "The returned buffer is only valid until the next call to ReadLine." Which means that the reader is free to reuse that memory later on, and that's what's happening.

When you do the cast in the proper way, string(ip), Go copies the contents of the buffer into the newly-created string, which remains valid in the future. But when you type-pun the slice into a string, you keep the exact same pointer, which stops working as soon as the reader refills its buffer.

If you decided to do the pointer trickery as a performance hack to avoid copying and allocation... too bad. The reader interface is going to force you to copy the data out anyway, and since it does, you should just use string().

huangapple
  • 本文由 发表于 2022年12月13日 14:47:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/74780926.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定