
go 语言禁止对函数调用表达式(如 time.now())直接取地址,因其返回值不属于“可寻址”(addressable)对象;唯一合规方式是先赋值给变量,再取其地址。
go 语言禁止对函数调用表达式(如 time.now())直接取地址,因其返回值不属于“可寻址”(addressable)对象;唯一合规方式是先赋值给变量,再取其地址。
在 Go 中,& 操作符仅能作用于可寻址的表达式(addressable expressions)。根据 Go 语言规范,可寻址的对象必须满足以下之一:
- 变量(例如 t := time.Now(); &t)
- 指针解引用(例如 &(*p))
- 切片索引操作(例如 &s[0])
- 可寻址结构体的字段(例如 &s.Field)
- 可寻址数组的索引(例如 &a[5])
⚠️ 函数调用(如 time.Now())和方法调用(如 x.String())明确不在该列表中——因此 &time.Now() 是语法错误,编译器会报错:cannot take the address of time.Now()。
为什么不允许?——底层与设计考量
从实现角度看,函数返回值可能位于 CPU 寄存器或栈帧中:
- 若值暂存在寄存器(常见于小返回值优化),它根本没有内存地址;
- 若暂存在栈上,其生命周期仅限于当前函数调用栈帧;若允许取地址并逃逸(escape),将导致悬垂指针(dangling pointer),破坏内存安全。
Go 的设计哲学强调显式优于隐式:如果需要一个可寻址的值,开发者必须显式声明变量来承载它,而非依赖编译器“悄悄分配内存”。这既保证了行为可预测,也避免了因隐式堆分配带来的性能模糊性。
正确写法:先赋值,再取址
package main
import (
"fmt"
"time"
)
func main() {
// ✅ 正确:先绑定到变量,再取地址
now := time.Now()
ptr := &now
fmt.Printf("Time: %v, Address: %p\n", *ptr, ptr)
}进阶技巧:封装为辅助函数(保持简洁性)
若频繁需要 *time.Time,可封装成一行式辅助函数(注意:本质仍是先赋值):
func nowPtr() *time.Time {
t := time.Now()
return &t // t 在函数内有效,返回其地址 → 编译器自动将其分配到堆(escape analysis)
}
// 使用
t := nowPtr() // t 类型为 *time.Time? 提示:return &t 触发逃逸分析(escape analysis),编译器会将 t 分配到堆上,确保返回的指针长期有效。可通过 go build -gcflags="-m" 验证。
常见误区澄清
- ❌ &struct{X int}{1} 是合法的 —— 因为复合字面量(composite literal)属于规范中明确允许取址的特例;
- ❌ &f()(无论 f 返回单值还是多值)始终非法;
- ❌ 试图用 unsafe 绕过限制不仅不可移植,且违反内存安全模型,绝对不推荐。
总结
| 场景 | 是否允许 &expr | 原因 |
|---|---|---|
| &time.Now() | ❌ 编译失败 | 函数调用非 addressable |
| t := time.Now(); &t | ✅ 合法 | 变量是 addressable |
| &struct{A int}{42} | ✅ 合法 | 复合字面量是 addressable 特例 |
简言之:Go 要求“可寻址性”必须由程序员显式建立,而非由语言隐式提供。 接受这一约束,不仅能写出更清晰、更安全的代码,也更贴近 Go 的工程化设计本意。










