为何*(*string)(unsafe.Pointer(&ip))转换bufio.Reader读取的字节数组异常?
我有一个存储IP段的文件,内容如下:
1.1.1.0/24 1.1.2.0/24 2.2.1.0/24 2.2.2.0/24
尝试将文件内容读取到字符串切片中,使用*(*string)(unsafe.Pointer(&ip))将[]byte转换为string时出现异常,测试代码如下:
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(ip)转换则运行正常。请问该异常的原因是什么?
bufio.Reader的缓冲区复用特性
bufio.Reader内部维护了一块共享缓冲区,每次调用ReadLine()返回的[]byte并非新分配的内存,而是指向这块缓冲区的切片。循环中每次拿到的ip变量,底层指向的是同一块内存区域,只是根据读取内容调整了切片的长度。unsafe转换是内存引用,无数据复制
用*(*string)(unsafe.Pointer(&ip))转换[]byte到string,本质是让string直接引用ip指向的共享缓冲区内存,没有复制任何数据。这导致切片中存储的所有string,底层都指向同一个缓冲区。后续读取覆盖缓冲区内容
当循环执行后续的ReadLine()时,bufio.Reader会用新读取的IP段覆盖缓冲区内容。此时,之前存入切片的string因为指向同一块内存,内容会被新数据覆盖。最后一次读取的2.2.2.0/24覆盖了缓冲区,所以切片中第一个元素的内容也变成了这个值。string(ip)转换的安全性
使用string(ip)转换时,Go会自动把[]byte中的数据复制到string的独立底层数组中,每个string都拥有专属内存,不会被后续的缓冲区更新影响,因此结果正常。
内容的提问来源于stack exchange,提问作者jx.wtj

