多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] 转载:https://studygolang.com/articles/2909 ## 概述 在Go语言中,Slice本质是什么呢?是一个reflect.SliceHeader结构体和这个结构体中Data字段所指向的内存。String本质是什么呢?是一个reflect.StringHeader结构体和这个结构体所指向的内存。 在Go语言中,指针的本质是什么呢?是unsafe.Pointer和uintptr。 当你清楚了它们的本质之后,你就可以随意的玩弄它们,嘿嘿嘿。 ## 获得 Slice 和 String 的内存数据 有一个 CGO 接口要调用,需要你把一个字符串数据或者字节数组数据从Go这边传递到C那边. 查了各种教程和文档,它们都告诉你要用 C.GoString 或 C.GoBytes 来转换数据。 但是,当你调用这两个函数的时候,发生了什么事情呢?这时候Go复制了一份数据,然后再把新数据的地址传给C,因为Go不想冒任何风险。 **你的C程序只是想一次性的用一下这些数据,也不得不做一次数据复制,这对于一个性能癖来说是多麽可怕的一个事实**! 这时候我们就需要一个黑魔法,来做到不拷贝数据又能把指针地址传递给C。 ``` // returns &s[0], which is not allowed in go func stringPointer(s string) unsafe.Pointer { p := (*reflect.StringHeader)(unsafe.Pointer(&s)) return unsafe.Pointer(p.Data) } // returns &b[0], which is not allowed in go func bytePointer(b []byte) unsafe.Pointer { p := (*reflect.SliceHeader)(unsafe.Pointer(&b)) return unsafe.Pointer(p.Data) } ``` 以上就是黑魔法第一式,我们先去到Go字符串的指针,它本质上是一个 *reflect.StringHeader ,但是Go告诉我们这是一个 *string ,我们告诉Go它同时也是一个 unsafe.Pointer ,Go说好吧它是,于是你得到了unsafe.Pointer,接着你就躲过了Go的监视,偷偷的把unsafe.Pointer转成了 *reflect.StringHeader 。 有了 *reflect.StringHeader ,你很快就取到了Data字段指向的内存地址,它就是Go保护着不想给你看到的隐秘所在,你把这个地址偷偷告诉给了C,于是C就愉快的偷看了Go的隐私。 ## 把string转化为 *C.char ``` package main /* #include <stdio.h> int test1(const char* s) { puts(s); } */ import "C" import ( "reflect" "unsafe" ) func stringPointer(s string)unsafe.Pointer { p := (*reflect.StringHeader)(unsafe.Pointer(&s)) return unsafe.Pointer(p.Data) } func GetCString(s string)*C.char { v := s + "\000" p := stringPointer(v) return (*C.char)(p) } func main() { s := "ahello" C.test1(GetCString(s)) } ``` 思考为什么 v := s + "\000" 字符串后面要加 "\000".我尝试了,如果不加转换会报错.如果加上 "\\\0" 则字符串打印出来结尾会多一个 \0 可以自己尝试一下 ## 把 []byte 转成 string 普通的转换方法是 ``` b := []byte("hello world") c := string(b) // []byte => string d := []byte(c) // string =>> []byte ``` 使用指针 unsafe.Pointe 来转换 ``` package labs28 import "testing" import "unsafe" func Test_ByteString(t *testing.T) { var x = []byte("Hello World!") var y = *(*string)(unsafe.Pointer(&x)) var z = string(x) if y != z { t.Fail() } } func Benchmark_Normal(b *testing.B) { var x = []byte("Hello World!") for i := 0; i < b.N; i ++ { _ = string(x) } } func Benchmark_ByteString(b *testing.B) { var x = []byte("Hello World!") for i := 0; i < b.N; i ++ { _ = *(*string)(unsafe.Pointer(&x)) } } ```