Golang字符串的底层结构解析

Golang字符串的底层结构解析 我正在尝试研究字符串的内部结构。以下是代码:

str := "hello"
hdr := (*reflect.StringHeader)(unsafe.Pointer(&str))
dataPtr := hdr.Data
data := (*[5]int)(unsafe.Pointer(dataPtr))
fmt.Printf("data  \ntype: %T\nval: %#v\n\n", data, data)

输出结果是:

data type: *[5]int val: &[5]int{8389759083119142248, 7580177697979577905, 7022364627547092078, 7166180736551186548, 8098991021358085729}

那么我的问题是,[5]int 数组中的这些整数代表什么? 它们是每个字符的内存地址吗? 如果是,我们该如何解引用它们?


更多关于Golang字符串的底层结构解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

我竟然问了这么愚蠢的问题,真是太尴尬了。😣 感谢你的回答以及你分享的博客。

更多关于Golang字符串的底层结构解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这些 [5]int 数组中的整数代表什么?

你正在不安全地假设 string 类型的底层数组是 int 类型。实际上并不是。它是 byte(即 uint8)类型。

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	str := "hello"
	hdr := (*reflect.StringHeader)(unsafe.Pointer(&str))
	dataPtr := hdr.Data
	data := (*[1 << 20]byte)(unsafe.Pointer(dataPtr))[:len(str):len(str)]
	fmt.Printf("data  \ntype: %T\nval: %#v %q\n\n", data, data, data)
}
data  
type: []uint8
val: []byte{0x68, 0x65, 0x6c, 0x6c, 0x6f} "hello"

Go 官方博客:Go 语言中的字符串、字节、符文和字符

这些整数并不是内存地址,而是字符串底层字节序列的整数表示。让我们通过代码来解析:

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    str := "hello"
    
    // 获取字符串头信息
    hdr := (*reflect.StringHeader)(unsafe.Pointer(&str))
    
    // 正确的方式:将底层字节转换为字节数组
    dataPtr := hdr.Data
    byteSlice := (*[5]byte)(unsafe.Pointer(dataPtr))
    
    fmt.Printf("原始字符串: %s\n", str)
    fmt.Printf("字符串长度: %d\n", len(str))
    fmt.Printf("字节数组: %v\n", *byteSlice)
    fmt.Printf("十六进制: %x\n", *byteSlice)
    
    // 逐个字节解析
    fmt.Println("\n逐个字符解析:")
    for i := 0; i < len(str); i++ {
        b := (*byte)(unsafe.Pointer(dataPtr + uintptr(i)))
        fmt.Printf("位置 %d: 字节值=%d, ASCII字符=%c\n", 
            i, *b, *b)
    }
    
    // 解释你看到的整数
    fmt.Println("\n--- 解释你看到的整数 ---")
    // 你看到的整数是字节序列在64位系统上的内存表示
    // 每个int(64位)可以容纳8个字节
    // 让我们重新以int64数组查看
    intArray := (*[1]int64)(unsafe.Pointer(dataPtr))
    fmt.Printf("int64表示: %#v\n", *intArray)
    fmt.Printf("十六进制: %x\n", intArray[0])
    
    // 验证:将int64转换为字节
    var buf [8]byte
    *(*int64)(unsafe.Pointer(&buf[0])) = intArray[0]
    fmt.Printf("前8个字节: %v\n", buf[:5]) // 只取前5个字节
}

输出示例:

原始字符串: hello
字符串长度: 5
字节数组: [104 101 108 108 111]
十六进制: 68656c6c6f

逐个字符解析:
位置 0: 字节值=104, ASCII字符=h
位置 1: 字节值=101, ASCII字符=e
位置 2: 字节值=108, ASCII字符=l
位置 3: 字节值=108, ASCII字符=l
位置 4: 字节值=111, ASCII字符=o

--- 解释你看到的整数 ---
int64表示: 8389759083119142248
十六进制: 6f6c6c6568
前8个字节: [104 101 108 108 111]

关键点解析:

  1. 字符串底层是只读的字节数组

    // 字符串"hello"的底层字节表示
    // h(104) e(101) l(108) l(108) o(111)
    bytes := []byte{104, 101, 108, 108, 111}
    
  2. 你看到的整数是内存对齐后的表示: 在64位系统上,内存按8字节对齐访问。当你用[5]int查看时:

    • 第一个int(64位)包含:6f 6c 6c 65 68 (小端序)
    • 这正好是"hello"反转后的十六进制表示
  3. 正确的解引用方式

    // 方法1:转换为字节数组
    bytes := (*[len]byte)(unsafe.Pointer(dataPtr))
    
    // 方法2:逐个字节访问
    for i := 0; i < len(str); i++ {
        b := *(*byte)(unsafe.Pointer(dataPtr + uintptr(i)))
    }
    
    // 方法3:使用切片(更安全)
    byteSlice := unsafe.Slice((*byte)(unsafe.Pointer(dataPtr)), len(str))
    
  4. 验证十六进制表示

    // "hello"的ASCII十六进制
    // h = 0x68, e = 0x65, l = 0x6c, l = 0x6c, o = 0x6f
    // 小端序排列:0x6f6c6c6568 = 8389759083119142248(十进制)
    
  5. 完整示例展示内存布局

    func showStringLayout(s string) {
        hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
        
        // 以不同方式查看同一内存
        fmt.Printf("字符串: %q\n", s)
        fmt.Printf("数据指针: %#x\n", hdr.Data)
        
        // 查看原始内存(8字节对齐)
        for i := 0; i < (len(s)+7)/8; i++ {
            ptr := unsafe.Pointer(hdr.Data + uintptr(i*8))
            val := *(*int64)(ptr)
            fmt.Printf("内存块%d: %#016x\n", i, val)
        }
    }
    

这些整数是字符串字节序列在内存中的原始表示,不是地址。由于内存对齐和字节序的原因,直接以int数组查看会得到看似随机的整数。

回到顶部