译 | Stack Traces In Go(下)

简介: 译 | Stack Traces In Go(下)

现在让我们看看第二个参数,它是一个字符串。字符串也是引用类型,但此它的header value是不可变的。字符串的header value声明为三个word大小的结构,包含指向底层字节数组的指针和字符串的长度:

// String parameter value
"hello"
// String header values
Pointer: 0x425c0
Length:  0x5
// Declaration
main.Example(slice []string, `str string`, i int)
// Stack trace
main.Example(0x2080c3f50, 0x2, `0x4, 0x425c0`, 0x5, 0xa)

上述显示了stack trace中的第四、五个值如何匹配string参数。第四个值表示指向底层字节数组的指针,第五个值表示字符串的长度为5.字符串“hello”需要5个字节。这两个值表示字符串(即,第二个参数)header的每个值。

第三个参数是一个整数,它是一个word的值:

// Integer parameter value
10
// Integer value
Base 16: 0xa
// Declaration
main.Example(slice []string, str string, `i int`)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, `0xa`)

上述显示了stack trace中的最后一个值如何匹配整数参数。trace中的最后一个值是十六进制数0xa,它是值10.与该参数传递的值相同。该值代表第三个参数。

Methods

我们更改程序,将Example函数改为一个成员函数:

01 package main
02
03 import "fmt"
04
05 type trace struct{}
06
07 func main() {
08     slice := make([]string, 2, 4)
09
10     var t trace
11     t.Example(slice, "hello", 10)
12 }
13
14 func (t *trace) Example(slice []string, str string, i int) {
15     fmt.Printf("Receiver Address: %p\n", t)
16     panic("Want stack trace")
17 }

第5行通过声明一个新类型trace,改变原始程序,转换Example函数为的第14行的成员函数。转换是通过重新声明函数包含一个trace类型的指针接收器来实现。然后在第10行,声明一个trace类型的t变量,并在第11行使用该变量进行方法调用。

由于该方法是使用指针接收器声明的,因此Go将获取t变量的地址以支持接收器类型,即使方法调用使用的是值。这次运行程序时,stack trace略有不同

Receiver Address: `0x1553a8`
panic: Want stack trace
01 goroutine 1 [running]:
02 main.(`*trace`).Example(`0x1553a8`, 0x2081b7f50, 0x2, 0x4, 0xdc1d0, 0x5, 0xa)
           /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
           temp/main.go:16 +0x116
03 main.main()
           /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
           temp/main.go:11 +0xae

首先,应该注意的是第02行的stack trace清楚表明这是一个使用指针接收器的方法调用。现在,包含(* trace)的成员函数的名称显示在包名称和方法名称之间。其次,要注意的是值列表现在如何第一个显示接收器的值。成员函数调用实际上是第一个参数是接收器值的函数调用。我们从stack trace中看到了这个实现细节。

由于声明或调用Example方法没有其他任何更改,因此所有其他值保持不变。调用Example的行号和发生panic的位置发生了变化,并反映了新代码。

Packing

如果有多个参数适合放入单个word,那么stack trace中参数的值将打包在一起:

01 package main
02
03 func main() {
04     Example(true, false, true, 25)
05 }
06
07 func Example(b1, b2, b3 bool, i uint8) {
08     panic("Want stack trace")
09 }

上述显示了一个新的示例程序,将Example函数更改为接受四个参数。前三个是布尔值,最后一个是八位无符号整数。布尔值也是一个8位值,因此所有四个参数都适合放入32位和64位架构上的单个word。当程序运行时,它会产生一个有趣的stack trace:

01 goroutine 1 [running]:
02 main.Example(`0x19010001`)
           /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
           temp/main.go:8 +0x64
03 main.main()
           /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
           temp/main.go:4 +0x32

对Example的调用,stack trace中没有四个值,取而代之只有一个值。所有四个独立的8位值都拼凑成一个word:

// Parameter values
true, false, true, 25
// Word value
Bits    Binary      Hex   Value
00-07   0000 0001   `01`    true
08-15   0000 0000   `00`    false
16-23   0000 0001   `01`    true
24-31   0001 1001   `19`    25
// Declaration
main.Example(`b1, b2, b3 bool, i uint8`)
// Stack trace
main.Example(`0x19010001`)

上述显示出了在stack trace的值如何匹配传入的四个参数值。true的8位值对应于1的值,false的值对应0值。二进制25的值是11001,十六进制转换为19。现在,当我们查看堆stack trace中表示的十六进制值时,我们会看到它如何表示传入的值。

结论

Go runtime提供了大量信息来帮助我们调试程序。在这篇文章中,我们专注于stack trace。解码在整个调用堆栈中传递给每个函数的值的能力是巨大的。它不止一次帮助我快速识别我的bug。既然您已经知道如何读取stack trace,那么希望您可以在下次发生stack trace时可以利用这些知识。

原文:stack-traces-in-go

本文作者 : cyningsun

本文地址https://www.cyningsun.com/07-05-2019/live-stack-traces-in-go-cn.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Golang

  1. 译|There Are No Reference Types in Go
  2. Go 语言没有引用类型,指针也与众不同
  3. 译|What “accept interfaces, return structs” means in Go
  4. 如何用好 Go interface
  5. 一个优雅的 LRU 缓存实现

目录
相关文章
|
程序员 Go
译 | Stack Traces In Go(上)
译 | Stack Traces In Go
47 0
|
13天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
34 2
|
12天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
21 2
|
12天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
25 2
|
2天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
2天前
|
测试技术 Go 索引
go语言使用 range 关键字遍历
go语言使用 range 关键字遍历
14 3
|
2天前
|
测试技术 Go 索引
go语言通过 for 循环遍历
go语言通过 for 循环遍历
10 3
|
4天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
3天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
13 3
|
7天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
28 4