
go 的字符串常量在编译期被固化到只读数据段,无论直接字面量还是 const 声明,均共享同一内存地址,不产生额外堆分配。
go 的字符串常量在编译期被固化到只读数据段,无论直接字面量还是 const 声明,均共享同一内存地址,不产生额外堆分配。
在 Go 中,字符串(string)本质上是一个不可变的、由底层字节数组和长度组成的结构体(StringHeader),其底层数据存储在程序的只读数据段(.rodata)中。关键在于:所有相同内容的字符串字面量(包括 const 声明的字符串)在编译时会被 Go 编译器自动“合并”(interning),指向同一块内存区域。 这是一种被广泛采用且稳定生效的编译期优化,虽未在语言规范中强制要求,但在当前所有 Go 版本(1.0+)中均一致实现。
以下示例可直观验证该行为:
package main
import (
"fmt"
"reflect"
"unsafe"
)
const Hello string = "hello"
func main() {
// 测试字面量
for i := 0; i < 3; i++ {
s := "hello"
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Printf("literal[%d]: %q → data ptr=%d, len=%d\n", i, s, hdr.Data, hdr.Len)
}
fmt.Println()
// 测试 const
for i := 0; i < 3; i++ {
s := Hello
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Printf("const[%d]: %q → data ptr=%d, len=%d\n", i, s, hdr.Data, hdr.Len)
}
}运行输出类似:
literal[0]: "hello" → data ptr=4870353, len=5 literal[1]: "hello" → data ptr=4870353, len=5 literal[2]: "hello" → data ptr=4870353, len=5 const[0]: "hello" → data ptr=4870353, len=5 const[1]: "hello" → data ptr=4870353, len=5 const[2]: "hello" → data ptr=4870353, len=5
可见:"hello" 字面量与 Hello 常量变量均指向完全相同的底层数据地址(Data 字段值一致),且长度均为 5。 这说明二者在运行时不分配新字符串对象——每次赋值仅复制一个轻量级的 StringHeader(16 字节,含指针+长度),而底层字节数据始终复用。
因此,回到原始问题:
- log.Println("hello") 与
- log.Println(HELLO)
在内存分配层面完全等价:均无堆分配(zero allocations),不创建新的字符串对象,也不触发 GC 压力。
⚠️ 注意事项:
- 此优化仅适用于编译期已知的字符串字面量和 const 字符串;若字符串来自运行时拼接(如 fmt.Sprintf("hello %d", i))、用户输入或 []byte 转换,则必然产生新分配。
- const 在 Go 中并非“宏替换”,而是类型安全的编译期绑定标识符;它不改变底层内存布局,但能提升代码可读性与维护性。
- 若需确保零分配日志,还可进一步使用 log.SetOutput(io.Discard) 或预分配缓冲区,但字符串本身已是最优。
总结:Go 的字符串常量与字面量共享底层只读内存,const 的核心价值在于语义清晰与类型安全,而非“节省内存”——因为不使用 const 时,Go 同样不会重复分配。真正影响性能的是是否引入运行时构造逻辑。

















