前言
本文是探讨的是"go语言中字符串不能修改"
此文章是个人学习归纳的心得, 为掘金首发 , 如有不对, 还望指正, 感谢!
字符串的结构
字符串对外呈现为string类型,但在runtime包中是stringStruct类型的
详情可看源码 github.com/golang/go/b…
// 字符串的
type stringStruct struct {
str unsafe.Pointer
len int
}
// 切片的
type slice struct {
array unsafe.Pointer
len int
cap int
}
在这个stringStruct结构体中,str是一个unsafe.Pointer类型的指针,len应该是字符串的长度,那str应该就是指向字符串实际地址的指针, 而Pointer又是什么类型呢?
我在github.com/golang/go/b… 找到了他
type Pointer *ArbitraryType
这玩意是个ArbitraryType类型的指针到重命名,在同一个文件里面,我们找到了ArbitraryType的定义
type ArbitraryType int
string类型的储存是用Unicode编码来存储的, 其本质还是用数字来表示,例如 a为97 b为98 c为99
在这个定义前面还有这样一段注释
// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
翻译就是:
任意类型(ArbitraryType)在此仅用于记录目的,实际上并不是不安全包的一部分。它表示任意 Go 表达式的类型。
所以到这里,我们就大概懂了字符串在runtime包的具体结构了
为什么字符串不能修改呢?
我看有大佬的解释是这样的:
"string通常指向字符串字面量,而字符串字面量存储的位置是只读段,而不是堆或者栈,所以才有了string不可以进行修改的约定"
go语言中只读段是指存储在程序的数据段或代码段中的只读内存区域。数据段是一块内存区域,用于存储全局变量和静态变量,而代码段是存储程序的机器指令的内存区域。
在程序执行过程中,计算机会从代码段中读取指令,并按照这些指令执行相应的操作。
在编译过程中,源代码会被编译成机器指令,然后这些指令会被存储在代码段中。当程序运行时,计算机会按照代码段中的指令来执行相应的操作,从而实现程序的功能。
代码段通常是只读的,这是为了保证程序的安全性。如果代码段可以被修改,那么可能会导致程序的行为不可预测或者出现安全漏洞。因此,代码段的内容是固定的,并且无法通过直接修改内存来改变它们。
通过字符串转切片进行修改
但是我们还是可以进行修改的,可以将字符串转为切片,字符串和切片的结构就只差了一个cap(容积),因此他们之间的转化是很容易的
在runtime包中生成字符串
在了解了上面的定义之后,我们再来分析,在runtime包中,使用gostringnocopy函数来生成字符串
如下面代码所示,声明一个yzz并赋值
var yzz string
yzz = "言志志"
字符串生成时,会先构建stringStruct对象,再转换成string,
gostringnocopy()函数的源码如下
//go:nosplit
func gostringnocopy(str *byte) string {
ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
s := *(*string)(unsafe.Pointer(&ss))
return s
}
下面是对代码的详细分析:
-
//go:nosplit是一个编译器指令,用于告诉编译器不要在该函数中插入栈分裂代码。栈分裂是一种优化手段,用于在某些情况下减少函数调用栈的大小。在这个函数中,我们不希望发生栈分裂,因为它使用了一些不安全的操作。 -
函数签名
func gostringnocopy(str *byte) string表示该函数接收一个指向byte类型的指针,并返回一个字符串。 -
stringStruct是一个结构体类型,用于表示字符串的结构。它包含两个字段:str表示指向字符串数据的指针,len表示字符串的长度。 -
ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}创建了一个stringStruct类型的变量ss,并初始化了它的字段值。str字段被赋值为str参数的指针,len字段被赋值为findnull(str)的结果。 -
findnull(str)是一个函数调用,它接收一个指向byte类型的指针,并返回该指针指向的字符串的长度。这个函数的具体实现没有在代码中给出,可能是一个自定义的函数。 -
s := *(*string)(unsafe.Pointer(&ss))是一个类型转换操作。首先,通过&ss取得ss变量的地址,然后通过unsafe.Pointer将其转换为一个指向string类型的指针,最后通过*解引用该指针得到一个string类型的值。这个操作的目的是将ss转换为字符串类型。 -
return s将转换后的字符串返回给调用者。