Go语言高级编程

324课时
1.4K学过
8分

2. 第2章 CGO编程

引言

2.1 快速入门

2.1.1 最简CGO程序

2.1.2 基于C标准库函数输出字符串

2.1.3 使用自己的C函数

2.1.4 C代码的模块化

2.1.5 用Go重新实现C函数

2.1.6 面向C接口的Go编程

2.2 CGO基础

2.2.1 import "C"语句

2.2.2 #cgo语句

2.2.3 build tag 条件编译

2.3 类型转换

2.3.1 数值类型

2.3.2 Go 字符串和切片

2.3.3 结构体、联合、枚举类型

2.3.4 数组、字符串和切片

2.3.5 指针间的转换

2.3.6 数值和指针的转换

2.3.7 切片间的转换

2.4 函数调用

2.4.1 Go调用C函数

2.4.2 C函数的返回值

2.4.3 void函数的返回值

2.4.4 C调用Go导出函数

2.5 内部机制

2.5.1 CGO生成的中间文件

2.5.2 Go调用C函数

2.5.3 C调用Go函数

2.6 实战: 封装qsort

2.6.1 认识qsort函数

2.6.2 将qsort函数从Go包导出

2.6.3 改进:闭包函数作为比较函数

2.6.4 改进:消除用户对unsafe包的依赖

2.7 CGO内存模型

2.7.1 Go访问C内存

2.7.2 C临时访问传入的Go内存

2.7.3 C长期持有Go指针对象

2.7.4 导出C函数不能返回Go内存

2.8 C++ 类包装

2.8.1 C++ 类到 Go 语言对象

2.8.1.1 准备一个 C++ 类

2.8.1.2 用纯C函数接口封装 C++ 类

2.8.1.3 将纯C接口函数转为Go函数

2.8.1.4 包装为Go对象

2.8.2 Go 语言对象到 C++ 类

2.8.2.1 构造一个Go对象

2.8.2.2 导出C接口

2.8.2.3 封装C++对象

2.8.2.4 封装C++对象改进

2.8.3 彻底解放C++的this指针

2.9 静态库和动态库

2.9.1 使用C静态库

2.9.2 使用C动态库

2.9.3 导出C静态库

2.9.4 导出C动态库

2.9.5 导出非main包的函数

2.10 编译和链接参数

2.10.1 编译参数:CFLAGS/CPPFLAGS/CXXFLAGS

2.10.2 链接参数:LDFLAGS

2.10.3 pkg-config

2.10.4 go get 链

2.10.5 多个非main包中导出C函数

课程评价 (0)

请对课程作出评价:
0/300

学员评价

暂无精选评价
30分钟

3.9.1 Delve入门

首先根据官方的文档正确安装Delve调试器。我们会先构造一个简单的Go语言代码,用于熟悉下Delve的简单用法。

创建main.go文件,main函数先通过循初始化一个切片,然后输出切片的内容:

package main

import (
	"fmt"
)

func main() {
	nums := make([]int, 5)
	for i := 0; i < len(nums); i++ {
		nums[i] = i * i
	}
	fmt.Println(nums)
}

命令行进入包所在目录,然后输入dlv debug命令进入调试:

$ dlv debug
Type 'help' for list of commands.
(dlv)

输入help命令可以查看到Delve提供的调试命令列表:

(dlv) help
The following commands are available:
    args ------------------------ Print function arguments.
    break (alias: b) ------------ Sets a breakpoint.
    breakpoints (alias: bp) ----- Print out info for active breakpoints.
    clear ----------------------- Deletes breakpoint.
    clearall -------------------- Deletes multiple breakpoints.
    condition (alias: cond) ----- Set breakpoint condition.
    config ---------------------- Changes configuration parameters.
    continue (alias: c) --------- Run until breakpoint or program termination.
    disassemble (alias: disass) - Disassembler.
    down ------------------------ Move the current frame down.
    exit (alias: quit | q) ------ Exit the debugger.
    frame ----------------------- Set the current frame, or execute command...
    funcs ----------------------- Print list of functions.
    goroutine ------------------- Shows or changes current goroutine
    goroutines ------------------ List program goroutines.
    help (alias: h) ------------- Prints the help message.
    list (alias: ls | l) -------- Show source code.
    locals ---------------------- Print local variables.
    next (alias: n) ------------- Step over to next source line.
    on -------------------------- Executes a command when a breakpoint is hit.
    print (alias: p) ------------ Evaluate an expression.
    regs ------------------------ Print contents of CPU registers.
    restart (alias: r) ---------- Restart process.
    set ------------------------- Changes the value of a variable.
    source ---------------------- Executes a file containing a list of delve...
    sources --------------------- Print list of source files.
    stack (alias: bt) ----------- Print stack trace.
    step (alias: s) ------------- Single step through program.
    step-instruction (alias: si)  Single step a single cpu instruction.
    stepout --------------------- Step out of the current function.
    thread (alias: tr) ---------- Switch to the specified thread.
    threads --------------------- Print out info for every traced thread.
    trace (alias: t) ------------ Set tracepoint.
    types ----------------------- Print list of types
    up -------------------------- Move the current frame up.
    vars ------------------------ Print package variables.
    whatis ---------------------- Prints type of an expression.
Type help followed by a command for full documentation.
(dlv)

每个Go程序的入口是main.main函数,我们可以用break在此设置一个断点:

(dlv) break main.main
Breakpoint 1 set at 0x10ae9b8 for main.main() ./main.go:7

然后通过breakpoints查看已经设置的所有断点:

(dlv) breakpoints
Breakpoint unrecovered-panic at 0x102a380 for runtime.startpanic()
    /usr/local/go/src/runtime/panic.go:588 (0)
        print runtime.curg._panic.arg
Breakpoint 1 at 0x10ae9b8 for main.main() ./main.go:7 (0)

我们发现除了我们自己设置的main.main函数断点外,Delve内部已经为panic异常函数设置了一个断点。

通过vars命令可以查看全部包级的变量。因为最终的目标程序可能含有大量的全局变量,我们可以通过一个正则参数选择想查看的全局变量:

(dlv) vars main
main.initdone· = 2
runtime.main_init_done = chan bool 0/0
runtime.mainStarted = true
(dlv)

然后就可以通过continue命令让程序运行到下一个断点处:

(dlv) continue
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x10ae9b8)
     2:
     3: import (
     4:         "fmt"
     5: )
     6:
=>   7: func main() {
     8:         nums := make([]int, 5)
     9:         for i := 0; i < len(nums); i++ {
    10:                 nums[i] = i * i
    11:         }
    12:         fmt.Println(nums)
(dlv)

输入next命令单步执行进入main函数内部:

(dlv) next
> main.main() ./main.go:8 (PC: 0x10ae9cf)
     3: import (
     4:         "fmt"
     5: )
     6:
     7: func main() {
=>   8:         nums := make([]int, 5)
     9:         for i := 0; i < len(nums); i++ {
    10:                 nums[i] = i * i
    11:         }
    12:         fmt.Println(nums)
    13: }
(dlv)

进入函数之后可以通过args和locals命令查看函数的参数和局部变量:

(dlv) args
(no args)
(dlv) locals
nums = []int len: 842350763880, cap: 17491881, nil

因为main函数没有参数,因此args命令没有任何输出。而locals命令则输出了局部变量nums切片的值:此时切片还未完成初始化,切片的底层指针为nil,长度和容量都是一个随机数值。

再次输入next命令单步执行后就可以查看到nums切片初始化之后的结果了:

(dlv) next
> main.main() ./main.go:9 (PC: 0x10aea12)
     4:         "fmt"
     5: )
     6:
     7: func main() {
     8:         nums := make([]int, 5)
=>   9:         for i := 0; i < len(nums); i++ {
    10:                 nums[i] = i * i
    11:         }
    12:         fmt.Println(nums)
    13: }
(dlv) locals
nums = []int len: 5, cap: 5, [...]
i = 17601536
(dlv)

此时因为调试器已经到了for语句行,因此局部变量出现了还未初始化的循环迭代变量i。

下面我们通过组合使用break和condition命令,在循环内部设置一个条件断点,当循环变量i等于3时断点生效:

(dlv) break main.go:10
Breakpoint 2 set at 0x10aea33 for main.main() ./main.go:10
(dlv) condition 2 i==3
(dlv)

然后通过continue执行到刚设置的条件断点,并且输出局部变量:

(dlv) continue
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0x10aea33)
     5: )
     6:
     7: func main() {
     8:         nums := make([]int, 5)
     9:         for i := 0; i < len(nums); i++ {
=>  10:                 nums[i] = i * i
    11:         }
    12:         fmt.Println(nums)
    13: }
(dlv) locals
nums = []int len: 5, cap: 5, [...]
i = 3
(dlv) print nums
[]int len: 5, cap: 5, [0,1,4,0,0]
(dlv)

我们发现当循环变量i等于3时,nums切片的前3个元素已经正确初始化。

我们还可以通过stack查看当前执行函数的栈帧信息:

(dlv) stack
0  0x00000000010aea33 in main.main
   at ./main.go:10
1  0x000000000102bd60 in runtime.main
   at /usr/local/go/src/runtime/proc.go:198
2  0x0000000001053bd1 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:2361
(dlv)

或者通过goroutine和goroutines命令查看当前Goroutine相关的信息:

(dlv) goroutine
Thread 101686 at ./main.go:10
Goroutine 1:
  Runtime: ./main.go:10 main.main (0x10aea33)
  User: ./main.go:10 main.main (0x10aea33)
  Go: /usr/local/go/src/runtime/asm_amd64.s:258 runtime.rt0_go (0x1051643)
  Start: /usr/local/go/src/runtime/proc.go:109 runtime.main (0x102bb90)
(dlv) goroutines
[4 goroutines]
* Goroutine 1 - User: ./main.go:10 main.main (0x10aea33) (thread 101686)
  Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:292 \
                runtime.gopark (0x102c189)
  Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:292 \
                runtime.gopark (0x102c189)
  Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:292 \
                runtime.gopark (0x102c189)
(dlv)

最后完成调试工作后输入quit命令退出调试器。至此我们已经掌握了Delve调试器器的简单用法。