惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 【当耐特】
博客园 - 聂微东
MyScale Blog
MyScale Blog
T
The Blog of Author Tim Ferriss
F
Full Disclosure
aimingoo的专栏
aimingoo的专栏
H
Help Net Security
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
D
DataBreaches.Net
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
Cyberwarzone
Cyberwarzone
S
Schneier on Security
Know Your Adversary
Know Your Adversary
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
云风的 BLOG
云风的 BLOG
T
Threatpost
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 司徒正美
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Announcements
Recent Announcements
罗磊的独立博客
L
LangChain Blog
L
Lohrmann on Cybersecurity
The GitHub Blog
The GitHub Blog
P
Palo Alto Networks Blog
The Cloudflare Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
M
MIT News - Artificial intelligence
WordPress大学
WordPress大学
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
S
Securelist
NISL@THU
NISL@THU
L
LINUX DO - 最新话题
PCI Perspectives
PCI Perspectives
V2EX - 技术
V2EX - 技术
IT之家
IT之家
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News | PayPal Newsroom
人人都是产品经理
人人都是产品经理
Google DeepMind News
Google DeepMind News
T
The Exploit Database - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
H
Heimdal Security Blog
Help Net Security
Help Net Security
V
V2EX

博客园 - ayanmw

极度推荐: 9router 一个npm服务,可以让你将白嫖到的所有AI token以及付费API都集中到一起 开源项目介绍 OpenTeam 命令行 检测验证网站的 SSL证书 是否安全 Nginx 使用自签名 SSL 证书 关闭夸克浏览器在windows资源管理器中,图片右键菜单的万能转换开关 golang的defer 深坑 Windows Terminal 清屏方法 Ctrl+Shift+K 免费二级域名以及设置SSL证书和解析 docker加速镜像 golang + AI 写一个可以 一键让nas下载百度网盘链接的文件 的程序 raid 为什么而不可以两个硬盘交叉读写和交叉备份? google-protobuf库 在golang语言下的插件扩展 golang json库 忽略 omitempty Go语言: golang如何判断一个结构体的一个方法是匿名组合的,还是该结构体自己的方法? golang 获得一个结构体的字节大小 吐槽 WPS 流氓行为: WPS 未经用户允许, 就建立了 WPS本地云盘 , 然后 云文档的文件 莫名其妙的的被删除了, 现在只能开会员恢复WPS云空间回收站的文件. 预测未来会有 内嵌AI大模型的游戏 好奇: windows10+都可以运行多个linux子系统了,为什么不支持运行多个windows子系统呢? gorm使用事务并发情况下切有最大mysql连接数限制的情况下的BUG,踩坑了 2024年 个人养老金 账户 应知应会 两个Mysql唯一索引的交换: 避免重复索引 Duplicate entry '3' for key 'priority_UNIQUE'
golang 空切片和nil切片 有区别吗?
ayanmw · 2025-02-28 · via 博客园 - ayanmw

判空

我们写程序都会有进行判空的操作:

对于map

对于golang的map,它为nil,你是无法直接使用的 如var oneMap map[uint32]uint32, 不赋值使用直接panic,必定要判空; 给它赋值为一个初始值var oneMap = map[uint32]uint32{}
使用的时候

if oneMap!=nil{//
  oneMap[1]=1
}

对于slice

初始化:

func Test_EmptySlice1(t *testing.T) {
	var emptySlice1 []int
	var emptySlice2 = []int{}
	if emptySlice1 == nil {
		t.Logf("emptySlice1==nil")
	}
	if emptySlice2 == nil {
		t.Logf("emptySlice2==nil")
	}
}

您会得到答案:

emptySlice1==nil

emptySlice2 就这么被忽略了, 这显然是不对的!

所以探索一下 slice 底层 空slice 和 nil slice的区别到底是什么?

探索runtime/slice.go 源码

slice的runtime源码为:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

这个结构体包含三个字段:
array:指向底层数组的指针。
len:切片的长度。
cap:切片的容量。

下面我们来详细探讨空切片和 nil 切片在源码层面的不同。

1. 空切片

空切片是指长度为 0 的切片,它可以通过 make 函数创建,也可以使用字面量创建。例如:

// 使用 make 函数创建
emptySlice1 := make([]int, 0)
// 使用字面量创建
emptySlice2 := []int{}

对于空切片,slice 结构体的各个字段值如下:

  • array:作为一个指针指向一个地址,但是这个地址是不存数据的。
  • len:值为 0,表示切片的长度为 0。
  • cap:值为 0,表示切片的容量为 0。

2. nil 切片

nil 切片是指未初始化的切片,它没有指向任何底层数组。例如:

var nilSlice []int

对于 nil 切片,slice 结构体的各个字段值如下:

  • array:值为 nil,表示没有指向任何底层数组。
  • len:值为 0,表示切片的长度为 0。
  • cap:值为 0,表示切片的容量为 0。

3. 源码层面的区别

在 Go 语言的源码中,空切片和 nil 切片的主要区别在于 array 字段的值。空切片的 array 字段指向一个空的底层数组,而 nil 切片的 array 字段为 nil。.

以下是一个简单的示例,展示了空切片和 nil 切片在内存中的不同:


func Test_EmptySlice(t *testing.T) {
	type UserShip struct {
		Id int
	}
	type Slice struct {
		Array unsafe.Pointer
		Len   int
		Cap   int
	}

	emptySlice := make([]*UserShip, 0)
	// 打印切片的长度、容量和底层数组的地址
	theVal := (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("Length: %d, Capacity: %d, Array address: %p theVal=%#v , ptrIntVal=%v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal, *(*int)(theVal.Array))

	// 尝试向空切片添加元素
	newShip := &UserShip{}
	emptySlice = append(emptySlice, newShip)
	theVal = (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
	emptySlice = append(emptySlice, newShip)
	theVal = (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
	emptySlice = append(emptySlice, newShip)
	theVal = (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
	{
		emptySlice2 := []uint32{}
		t.Logf("Length: %d, Capacity: %d, Array address: %p",
			len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
	}
	{
		emptySlice2 := []uint32{}
		t.Logf("Length: %d, Capacity: %d, Array address: %p",
			len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
	}
	{
		emptySlice2 := []uint32{}
		t.Logf("Length: %d, Capacity: %d, Array address: %p",
			len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
	}
}

输出结果:

=== RUN   Test_EmptySlice
    deepcopy_test.go:37: Length: 0, Capacity: 0, Array address: 0x16cb8c0 theVal=&common.Slice{Array:(unsafe.Pointer)(0x16cb8c0), Len:0, Cap:0} , ptrIntVal=0
    deepcopy_test.go:44: After append: Length: 1, Capacity: 1, Array address: 0xc0000a0000 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc0000a0000), Len:1, Cap:1}
    deepcopy_test.go:48: After append: Length: 2, Capacity: 2, Array address: 0xc000052050 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc000052050), Len:2, Cap:2}
    deepcopy_test.go:52: After append: Length: 3, Capacity: 4, Array address: 0xc0001206a0 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc0001206a0), Len:3, Cap:4}
    deepcopy_test.go:56: Length: 0, Capacity: 0, Array address: 0x16cb8c0
    deepcopy_test.go:61: Length: 0, Capacity: 0, Array address: 0x16cb8c0
    deepcopy_test.go:66: Length: 0, Capacity: 0, Array address: 0x16cb8c0
--- PASS: Test_EmptySlice (0.00s)

可以看到 无论nil还是 empty 的slice , 在赋值后,len长度发生变化后, 它的 array值都会发生变化.

且 多个 empty slice 它们的 array值 都是: 0x16cb8c0 ,这个值打印出来ptrIntVal=0,这个值大概率就是一个全局变量,值为0.

这一定是golang的优化, 一个 实例化的emptySlice ,大小为0, 应该是一个0数组的地址,简化就是一个 0值指针的变量地址. 虽然emptySlice array 有值, 但是 它 不包含任何元素。

实际影响

虽然空切片和 nil 切片在长度和容量上都是 0,但它们在某些情况下的行为是不同的。例如,在 JSON 编码时,空切片会被编码为 [],而 nil 切片会被编码为 null。

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    // 创建空切片
    emptySlice := make([]int, 0)
    // 创建 nil 切片
    var nilSlice []int

    // 对空切片进行 JSON 编码
    emptyJSON, _ := json.Marshal(emptySlice)
    // 对 nil 切片进行 JSON 编码
    nilJSON, _ := json.Marshal(nilSlice)

    fmt.Printf("Empty slice JSON: %s\n", string(emptyJSON))
    fmt.Printf("Nil slice JSON: %s\n", string(nilJSON))
}

输出结果

Empty slice JSON: []
Nil slice JSON: null

综上所述,空切片和 nil 切片在源码层面的主要区别在于 array 字段的值,这也导致了它们在某些情况下的行为不同。