go编译器不保证跨包字符串字面量去重,同一包内const字符串可能合并,但取地址或跨包时各存独立副本;纯常量拼接可编译期合成,含变量则运行时拼接;手动提取高频字符串常量反而增加体积和开销。

Go 编译器不保证跨包、跨文件的字符串字面量去重,相同内容的 "<td>" 或 <code>"{"status":"ok"}" 在二进制中大概率各自保留独立副本。
同一包内 const 字符串通常会被合并
编译器在 SSA 优化阶段会对同一编译单元(即同一个 .go 文件,或同一包内经内联后可见的常量)做常量折叠。多个 const s1 = "hello"、const s2 = "hello" 最终可能只生成一份只读数据。
- 实测可用
go tool objdump -s main\.main your_binary | grep "hello"查看地址是否唯一 - 但若其中一个被取地址(如
&s1),则可能强制保留独立副本(因需稳定地址) -
var s = "hello"不参与此优化,每次声明都分配新string头 + 新底层数组
跨包字符串字面量几乎从不合并
这是 Go 当前明确不承诺的行为。即使 pkgA 和 pkgB 都定义了 const TagTD = "<td>",链接后二进制中大概率存在两份 <code>.rodata 段数据。
- 根源是 Go 语言规范未规定字符串存储方式,issue #5160 至今 open,官方无计划引入全局 interning
- 构建可重现性优先于空间优化:避免隐式共享导致不同包编译顺序影响二进制内容
- 使用
go tool compile -S可见不同包中相同字面量生成独立符号(如go.string."<td>" 出现多次) <h3>拼接常量字符串在编译期直接合成</h3> <p>像 <code>""这类含变量的表达式不会合并;但纯常量拼接,如"<t>"</t>或"<td>" + "</td>",会在编译期直接合成为单个字面量"<td></td>"。- 该优化仅适用于所有操作数均为编译期已知常量(包括
const和字面量) - 一旦含变量(哪怕只是
const s = "a"; t := s + "b"),就退化为运行时拼接,不触发合并 - 这种合成不减少最终二进制中原始字面量的份数——它只是把多个常量替换成一个更长的新常量
别手动提取“高频字符串”到全局 const
在模板生成、HTML 片段硬编码等场景,开发者容易本能地把
"<div>"、<code>"class"提取为全局常量。这反而可能增加二进制体积和间接访问开销。- 提取后若该 const 被跨包引用,会强制每个引用点都带一份
string头(指针+长度),而原字面量本身已在只读段 - 现代链接器对同文件内重复字面量已有基础合并,手动抽象无收益,还破坏局部性
- 真正值得抽象的是逻辑含义明确的组合(如
func TD(content string) string { return "<td>" + content + "</td>" }),而非单纯字面量复用
真正影响二进制大小的从来不是几十个字节的字符串重复,而是大块静态数据(如嵌入的 JSON Schema、HTML 模板文件)或未清理的调试符号。字符串字面量去重属于过早优化,且 Go 编译器没给你这个保证。
- 该优化仅适用于所有操作数均为编译期已知常量(包括











