
本教程深入探讨go语言中指针地址的理解,特别是在处理sync.waitgroup等返回指针的场景。文章将通过具体代码示例,详细解释变量地址与变量所指向值地址之间的关键区别,纠正常见的打印误区,并指导读者如何正确获取和输出目标内存地址,以避免混淆和确保程序行为符合预期。
在Go语言中,指针是一种特殊的变量,它存储另一个变量的内存地址。理解指针的关键在于区分“变量本身的值”和“变量的内存地址”。
例如,如果 x 是一个 int 类型的变量,那么 &x 的类型是 *int,它存储了 x 的内存地址。
考虑以下Go语言代码片段,它展示了一个使用sync.WaitGroup进行并发同步的常见模式:
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func Run() *sync.WaitGroup {
var wg sync.WaitGroup // 声明一个 WaitGroup 实例
wg.Add(1)
go func() {
defer wg.Done()
fmt.Printf("goroutine 内部 wg 实例地址: %p\n", &wg) // 打印 wg 实例的地址
time.Sleep(5 * time.Second)
fmt.Println("goroutine 唤醒")
}()
fmt.Printf("Run 函数返回前 wg 实例地址: %p\n", &wg) // 打印 wg 实例的地址
return &wg // 返回 wg 实例的地址
}
func main() {
runtime.GOMAXPROCS(3)
wg := Run() // main 函数中的 wg 接收 Run 函数返回的地址,此时 wg 本身是一个指针变量
fmt.Printf("main 函数中 wg 变量的地址: %p\n", &wg) // 打印 wg 这个指针变量本身的地址
wg.Wait()
fmt.Println("main 函数结束")
}这段代码的典型输出可能如下所示:
立即学习“go语言免费学习笔记(深入)”;
Run 函数返回前 wg 实例地址: 0xc000014080 main 函数中 wg 变量的地址: 0xc00000e028 goroutine 内部 wg 实例地址: 0xc000014080 goroutine 唤醒 main 函数结束
观察输出,Run函数内部和goroutine内部打印的wg地址是相同的 (0xc000014080),这符合预期,因为它们都引用的是Run函数中创建的同一个WaitGroup实例。然而,main函数中打印的wg地址 (0xc00000e028) 却与前两者不同,这常常会引起困惑。
造成上述地址差异的原因在于对Go语言中指针变量的理解不足。让我们详细解析:
在Run函数中:
在main函数中:
简而言之,&wg(当wg本身是*sync.WaitGroup类型时)获取的是存储该指针的内存位置,而wg(当wg本身是*sync.WaitGroup类型时)获取的是该指针所指向的sync.WaitGroup实例的内存位置。
要正确地在main函数中打印WaitGroup实例的地址,我们需要直接打印wg变量的值:
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func Run() *sync.WaitGroup {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Printf("goroutine 内部 wg 实例地址: %p\n", &wg)
fmt.Println("goroutine 开始睡眠 5s")
time.Sleep(5 * time.Second)
fmt.Println("goroutine 唤醒")
}()
fmt.Printf("Run 函数返回前 wg 实例地址: %p\n", &wg)
return &wg
}
func main() {
runtime.GOMAXPROCS(3)
wg := Run()
// 修正后的打印,直接输出 wg (指针变量的值,即所指向的地址)
fmt.Printf("main 函数中 WaitGroup 实例的地址 (正确): %p\n", wg)
wg.Wait()
fmt.Println("main 函数结束")
}运行修正后的代码,你将看到如下输出:
Run 函数返回前 wg 实例地址: 0xc000014080 main 函数中 WaitGroup 实例的地址 (正确): 0xc000014080 goroutine 内部 wg 实例地址: 0xc000014080 goroutine 开始睡眠 5s goroutine 唤醒 main 函数结束
现在,所有打印的地址都是一致的,它们都指向了Run函数中创建的同一个sync.WaitGroup实例。
值得注意的是,Run函数中的wg变量虽然是局部变量,但由于它的地址&wg被作为返回值返回,Go编译器会进行逃逸分析(Escaping Analysis)。在这种情况下,wg实例不会被分配在栈上(通常局部变量会分配在栈上),而是会“逃逸”到堆上进行分配。这样做的目的是确保wg实例的生命周期可以超出Run函数的执行范围,因为它被main函数和goroutine引用了。这正是为什么main函数和goroutine能够访问到同一个有效的WaitGroup实例地址的原因。
本文通过一个sync.WaitGroup的实际案例,深入探讨了Go语言中指针地址的理解和正确打印方法。核心要点在于区分“一个指针变量本身的地址”和“这个指针变量所指向的地址”。当一个函数返回一个指针时,接收该返回值的变量本身就成为了一个指针变量。要获取它所指向的实际数据结构的地址,应直接使用该指针变量;而如果对这个指针变量再次使用&运算符,则会得到该指针变量自身的存储地址。掌握这一概念对于编写正确的Go并发程序和调试内存相关问题至关重要。
以上就是Go语言中sync.WaitGroup指针地址的深度解析与正确打印实践的详细内容,更多请关注php中文网其它相关文章!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号