Go 中的空结构体和空字符串

Go 中的空结构体和空字符串

在日常的编程过程中,大家应该经常能遇到各种"空"吧,比如空指针、空结构体、空字符串......代码中的这些"空"往往是特例,都有特殊的性质。

本文就以 Go 语言为例,一起来看看空结构体空字符串在 Go 语言中的特殊之处。

首先是空结构体。

Go 语言中的空结构体

我们先来运行这样一段代码。

go 复制代码
// https://go.dev/play/p/L2YxOr8k6Qq
package main

type U struct{}
type V struct{}

func main() {
    var i = 10

    var u = U{}
    var v = V{}

    println("i address =", &i)
    println("u address =", &u)
    println("v address =", &v)
}

// 运行结果
// i address = 0xc000046730
// u address = 0xc000046730
// v address = 0xc000046730

iuv 这 3 个变量的内存地址竟然完全一样!

uv 的内存地址相同就已经有点出乎意料了,毕竟它们的类型不同,一个是 struct U 的实例(值),一个是 struct V 的实例。但更出乎意料的是,这个内存地址竟然还是变量 i 的地址(如下图)。

这是因为 struct Ustruct V 都是空结构体 这种特殊的结构体,而空结构体的实例 ,即 struct{}{}不占用任何存储空间 ,图中自然也就找不到存储着 struct{}{} 的空间。

不占用存储空间且内存地址相同,这就是空结构体这种"空"的特点。

更有意思的是,既然 u (或 v)的地址就是变量 i 的地址,那通过 u 应该也能读出存储在 0xc000046730 这个位置的整数 10吧。 让我们来试一试。

go 复制代码
println(*(*int)(unsafe.Pointer(&u)))

果然可以!

下面我们再来看看另一种"空"------空字符串。

Go 语言中的空字符串

下面这段代码会输出什么呢?交替出现的 Sora 和空行吗?

go 复制代码
// https://go.dev/play/p/c1ZfChdH0rT
package main

import "fmt"

func main() {
    title := ""
    go func() {
        for {
            fmt.Println(title)
        }
    }()

    for {
        go func() {
            title = ""
        }()

        go func() {
            title = "Sora"
        }()
    }
}

竟然 painc 了,意不意外?

go 复制代码
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x462cca]

goroutine 18 [running]:
fmt.(*buffer).writeString(...)
    /usr/local/go-faketime/src/fmt/print.go:108
fmt.(*fmt).padString(0x425000000041f01c?, {0x0, 0x4})
    /usr/local/go-faketime/src/fmt/format.go:110 +0x24a
...
fmt.Println(...)
    /usr/local/go-faketime/src/fmt/print.go:314
main.main.func1()
    /tmp/sandbox3517788398/prog.go:9 +0x5a
created by main.main in goroutine 1
    /tmp/sandbox3517788398/prog.go:7 +0x66

下面就来分析一下背后的原因(从本节的标题也能猜出吧,八成和title = ""这里的空字符串有关)。

首先,由倒数第 3 行的 /tmp/sandbox3517788398/prog.go:9 +0x5a 可见,导致 panic 的代码是第 9 行的 fmt.Println(title)

而 "破案"的线索就在报错信息的这一行(第 7 行):

bash 复制代码
fmt.(*fmt).padString(0x425000000041f01c?, {0x0, 0x4})

接下来我们先找出 fmt.padString() 函数的定义。

go 复制代码
// https://github.com/golang/go/blob/master/src/fmt/format.go#L110

// padString appends s to f.buf, ...
func (f *fmt) padString(s string) {

对照着定义,可以猜出 {0x0, 0x4} 对应的正是参数 s string

那再结合字符串类型 string 在 Go 语言中的定义,

go 复制代码
// https://github.com/golang/go/blob/master/src/internal/unsafeheader/unsafeheader.go#L34
type String struct {
    Data unsafe.Pointer
    Len  int
}

不难推测出,这里相当于我们将 String{Data: 0x0, Len: 0x4} 这样一个表示字符串的结构体传递给了 fmt.padString()。而这是一个长度为 4 的空字符串

这里没有写错,就是长度为 4 的空字符串

既然长度为 4,那别管空不空,fmt.Println() 就要通过存在于 Data 中的指针(地址)取出这"4个字符"------计算机就是这么"诚实"。但 Data == 0x0,是空指针,当然就空指针 panic 了,即报错信息中的"invalid memory address or nil pointer dereference"。

"案子"是破了,可"长度为 4 的空字符串"又是怎么产生的呢?

罪魁祸首就在这一对儿协程上,

go 复制代码
    for {
        go func() {
            title = ""
        }()

        go func() {
            title = "Sora"
        }()
    }

看似通过 = 一下子就能把字符串赋给变量 title,但实际上不得不依次对 DataLen 赋值,比如,

ini 复制代码
go routine1: title.Data = <空字符串""的地址> = 0x0
go routine1: title.Len  = <空字符串""的长度> = 0

go routine2: title.Data = <字符串"Sora"的地址>
go routine2: title.Len  = <字符串"Sora"的长度> = 4

而当这一对儿协程并发执行时,以上 2 组"语句"的执行顺序是不确定的,完全有可能出现以下二者交替执行情况:

ini 复制代码
go routine2: title.Data = <字符串"Sora"的地址>
    go routine1: title.Data = <空字符串""的地址> = 0x0
    go routine1: title.Len  = <空字符串""的长度> = 0
go routine2: title.Len  = <字符串"Sora"的长度> = 4

于是导致了{Data: 0x0, Len: 0x4},即长度为 4 的空字符串

painc 的"案子"终于破了。


本文通过两个小例子简单介绍了 Go 语言中的"空",诸位也可以测试测试其他语言中的"空"有什么特性。

相关推荐
煎鱼eddycjy1 小时前
新提案:由迭代器启发的 Go 错误函数处理
go
煎鱼eddycjy2 小时前
Go 语言十五周年!权力交接、回顾与展望
go
不爱说话郭德纲18 小时前
聚焦 Go 语言框架,探索创新实践过程
go·编程语言
0x派大星2 天前
【Golang】——Gin 框架中的 API 请求处理与 JSON 数据绑定
开发语言·后端·golang·go·json·gin
IT书架2 天前
golang高频面试真题
面试·go
郝同学的测开笔记2 天前
云原生探索系列(十四):Go 语言panic、defer以及recover函数
后端·云原生·go
工业互联网专业2 天前
Python毕业设计选题:基于python的豆瓣电影数据分析可视化系统-flask+spider
vue.js·python·数据分析·flask·毕业设计·源码·课程设计
秋落风声3 天前
【滑动窗口入门篇】
java·算法·leetcode·go·哈希表
工业互联网专业4 天前
Python毕业设计选题:基于Spark的国漫推荐系统的设计与实现-django+spider
vue.js·python·spark·django·毕业设计·源码·课程设计
0x派大星5 天前
【Golang】——Gin 框架中的模板渲染详解
开发语言·后端·golang·go·gin