有时候为节约内存用了 C.CBytes 函数,这不往坑里跑了。c 里面字符串都是要以数字 0 结尾的。
为节约内存,想使用 C.CBytes 直接到 c 的 char *类型。但是有问题
package main
// #include <string.h>
import "C"
import (
"fmt"
)
func main() {
b := []byte("我是小妖怪,逍遥又自在,杀人不眨眼,吃人不放盐。")
bs := string(b)
p := C.CBytes(b)
fmt.Printf("%d:%d:%d\n", int(C.strlen((*C.char)(p))), len(b), int(C.strlen((*C.char)(C.CString(bs)))))
}
// 输出
// Cbytes.strlen(75): len(72):CString.strlen(72)
发现长度不对
既然 C.Bytes 这条路走不同,换个思路,用类型强转吧 []byte->string。再使用 C.CString 不就得了
// #include <string.h>
import "C"
import (
"fmt"
"unsafe"
)
func main() {
b := []byte("我是小妖怪,逍遥又自在,杀人不眨眼,吃人不放盐。")
bs := *(*string)(unsafe.Pointer(&b))
p := C.CString(bs)
fmt.Printf("strlen(%d)\n", C.strlen((*C.char)(p)))
}
package main
/*
#include<string.h>
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
b := []byte("我是小妖怪,逍遥又自在,杀人不眨眼,吃人不放盐。\x00")
p := (*C.char)(unsafe.Pointer(&b[0]))
fmt.Printf("[]byte addr:%p:%d\n", b, C.strlen(p))
fmt.Printf("%p\n", b) //告诉编译器b还在使用
}
1
reus 2019-10-22 09:43:37 +08:00 via Android
我想一般人都不会想到用 CBytes… 字符串当然直接用 CString。
CBytes 一样是复制一份,和 CString 的区别只在末尾的 0,哪来节约内存? |
2
guonaihong OP @reus 你的入参是[]byte。[]byte 到 string 要拷贝一次内存的。
|
3
fuis 2019-10-22 10:02:03 +08:00
为啥 C.Bytes 就节约内存了,没看懂
|
4
guonaihong OP @fuis 在 go 里面 []byte 到 string 是有一次内存拷贝的。想把 go 里面的[]byte 转成 c 的*C.char 常见做法有两次拷贝
[]byte-->string-->*c.char 本来想用 C.Bytes 节约[]byte->string 的一次拷贝,发现没加'\0‘。后面用强制类型转化搞定了。 package main import ( "fmt" ) func main() { s := "wowowo" b := []byte(s) fmt.Printf("%p:%p\n", &s, b) } |
5
petelin 2019-10-22 10:13:17 +08:00 via iPhone
@guonaihong 强转就没有拷贝了?那怎么写才可能有拷贝
|
6
reus 2019-10-22 10:13:45 +08:00 1
@guonaihong 入参是 []byte,那就不需要 string 啊,自己补个 0 不就行了。
b := []byte("我是小妖怪,逍遥又自在,杀人不眨眼,吃人不放盐。") cstr := C.CBytes(append(b, 0)) |
7
petelin 2019-10-22 10:20:53 +08:00 via iPhone
https://www.cnblogs.com/zhangboyu/p/7623712.html
我觉得直接让 string 的指针指向 byte 的地址然后保证 byte 不会再被修改更靠谱 |
8
reus 2019-10-22 10:27:56 +08:00
@petelin bs := *(*string)(unsafe.Pointer(&b)) 这一行就是你说的“让 string 的指针指向 byte 的地址”
但具体到这个情况,[]byte 到 *C.char 是不需要 string 的,直接在 []byte 最后补零,再用 C.CBytes 就行了 |
9
guonaihong OP @reus 加 0 的思路是好的。但是 append 发现空间不够还是会重新 malloc 内存。可以控制[]byte,留空一位专门放 0。
打印下面的代码证实, append 重新分配内存。 ```go package main import "C" import "fmt" func main() { b := []byte("我是小妖怪,逍遥又自在,杀人不眨眼,吃人不放盐。") b1 := append(b, 0) cstr := C.CBytes(b1) fmt.Printf("b(%p):append address(%p):cbytes(%p)\n", b, b1, cstr) } ``` |
10
guonaihong OP @fuis "为啥 C.Bytes 就节约内存了,没看懂"。
如果 C.Bytes 可以用,只是把内存分配的过程从两次变为 1 次。你可以看下 改进方法([]byte 到*C.char) --->(1 次分配内存) 极致省内存方法(不推荐) ---->(0 次分配内存) 这两个标题下面的方法,把内存分配从 1 次,变为 0 次。当然推荐 1 次的方法,不容易出错。 ## 内存两次分配的原因如下 要把 go 里面的[]byte 转成 c 里面的 char *。常规做法有两步[]byte-->string-->char *。每一步都有 malloc 内存的操作。 * 第一步 []byte--> string。(需要重新分配内存, 比如 string(bytes 类型的变量)) * 第二步 string --> char *。(需要重新分配内存,C.CString(s)) |
11
reus 2019-10-22 18:48:14 +08:00
@guonaihong append 是否分配内存,要看 cap,cap 够就不用分配,这是通用的规则。
|
12
guonaihong OP @reus 用 append 还要依赖传参 空间是否够。觉得这个方式不是最优,可以用类型转换。
|