英文:
How to handle char * from packed struct in cgo?
问题
由于Go语言不支持紧凑结构体(packed struct),我找到了一篇很棒的文章,用例子解释了如何在Go中使用紧凑结构体。链接在这里:https://medium.com/@liamkelly17/working-with-packed-c-structs-in-cgo-224a0a3b708b
问题是,当我尝试将char *替换为[10]char时,它不起作用。我不确定这个转换是如何处理[10]char而不是char *的。下面是从上述文章中提取并修改为char *的示例代码。
package main
/*
#include "stdio.h"
#pragma pack(1)
typedef struct{
unsigned char a;
char b;
int c;
unsigned int d;
char *e; // 从 char[10] 改为 char *
}packed;
void PrintPacked(packed p){
printf("\nFrom C\na:%d\nb:%d\nc:%d\nd:%d\ne:%s\n", p.a, p.b, p.c, p.d, p.e);
}
*/
import "C"
import (
"bytes"
"encoding/binary"
)
// GoPack 是C中紧凑结构体的Go版本
type GoPack struct {
a uint8
b int8
c int32
d uint32
e [10]uint8
}
// Pack 生成Go结构体的紧凑版本
func (g *GoPack) Pack(out *C.packed) {
buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, g)
*out = *(*C.packed)(C.CBytes(buf.Bytes()))
}
func main() {
pack := &GoPack{1, 2, 3, 4, [10]byte{}}
copy(pack.e[:], "TEST123")
cpack := C.packed{} // 仅用于分配内存,仍然受GC控制
pack.Pack(&cpack)
C.PrintPacked(cpack)
}
这是我第一次使用cgo,如果我在任何地方出错,请纠正我。
英文:
Since Go doesn’t support packed struct I found this great article explains everything with examples how to work with packed struct in go. https://medium.com/@liamkelly17/working-with-packed-c-structs-in-cgo-224a0a3b708b
The problem is when I try char * in place of [10]char it's not working. I'm not sure how this conversion works with [10]char and not with char * . Here is example code taken from above article and modified with char * .
package main
/*
#include "stdio.h"
#pragma pack(1)
typedef struct{
unsigned char a;
char b;
int c;
unsigned int d;
char *e; // changed from char[10] to char *
}packed;
void PrintPacked(packed p){
printf("\nFrom C\na:%d\nb:%d\nc:%d\nd:%d\ne:%s\n", p.a, p.b, p.c, p.d, p.e);
}
*/
import "C"
import (
"bytes"
"encoding/binary"
)
//GoPack is the go version of the c packed structure
type GoPack struct {
a uint8
b int8
c int32
d uint32
e [10]uint8
}
//Pack Produces a packed version of the go struct
func (g *GoPack) Pack(out *C.packed) {
buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, g)
*out = *(*C.packed)(C.CBytes(buf.Bytes()))
}
func main() {
pack := &GoPack{1, 2, 3, 4, [10]byte{}}
copy(pack.e[:], "TEST123")
cpack := C.packed{} //just to allocate the memory, still under GC control
pack.Pack(&cpack)
C.PrintPacked(cpack)
}
I'm working with cgo first time so correct me if i am wrong at any point.
答案1
得分: 1
你正在将 GoPack.e
的十个(零)字节写入类型为 char *
的 packed.e
中。这样做是行不通的,因为指针的大小取决于你的系统,通常为4或8个字节。即使这些字节表示一个有效的指针,你也会溢出分配的内存空间。
如果你想创建一个具有有效 packed.e
字段的结构体,你需要在 C 堆上分配10个字节的内存,将字节复制到其中,然后将 packed.e
指向这个分配的内存(在释放相应的 packed
结构体时,你也需要释放这块内存)。你不能直接使用 binary.Write
来实现这一点。
你可以以此作为起点:
buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, g.a)
binary.Write(buf, binary.LittleEndian, g.b)
binary.Write(buf, binary.LittleEndian, g.c)
binary.Write(buf, binary.LittleEndian, g.d)
binary.Write(buf, binary.LittleEndian, uintptr(C.CBytes(g.e)))
*out = *(*C.packed)(C.CBytes(buf.Bytes()))
函数 C.CBytes(b)
在 C 堆上分配了 len(b)
个字节的内存,并将 b
中的字节复制到其中,返回一个 unsafe.Pointer
。
请注意,我从你的代码中复制了 *out = *(*C.packed)...
这一行。实际上,这会导致内存泄漏和不必要的复制。也许更好的做法是使用一个直接将字节写入 out
指向的内存的写入器。
也许可以这样做:
const N = 10000 // 应该是 sizeof(*out) 或更大
buf := bytes.NewBuffer((*[N]byte)(unsafe.Pointer(out))[:])
这样就创建了一个 bytes.Buffer
,直接将数据写入 out
结构体,而不经过任何中间内存。请注意,由于使用了不安全的操作,如果写入的数据字节数超过 out
指向的内存大小,就会发生缓冲区溢出。
警告:这些操作都相当危险,容易出现与 C 语言相同的问题,而且你需要检查 cgo 指针规则,以确保不会受到垃圾回收的影响。建议你避免编写或包含此类代码,因为它可能引入隐蔽的问题,而且可能不会立即显现出来,尤其是考虑到你说自己对指针和内存分配的经验不多。
英文:
You are writing ten (zero) bytes of GoPack.e
into the packed.e
which is of type char *
. This won't work, because pointers will be 4 or 8 bytes depending on your system, so even if the bytes represented a valid pointer, you are overflowing the amount of memory allocated.
If you want to create a valid structure with a valid packed.e
field, you need to allocate 10 bytes of memory in the C heap, copy the bytes into that, and then point packed.e
to this allocated memory. (You will also need to free this memory when you free the corresponding packed
structure). You can't do this directly with binary.Write
.
You can take this as a starting point:
buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, g.a)
binary.Write(buf, binary.LittleEndian, g.b)
binary.Write(buf, binary.LittleEndian, g.c)
binary.Write(buf, binary.LittleEndian, g.d)
binary.Write(buf, binary.LittleEndian, uintptr(C.CBytes(g.e))
*out = *(*C.packed)(C.CBytes(buf.Bytes()))
The function C.CBytes(b)
allocates len(b)
bytes in the C heap, and copies the bytes from b
into it, returning an unsafe.Pointer
.
Note that I've copied your *out = *(*C.packed)...
line from your code. This actually causes a memory leak and an unnecessary copy. Probably it would be better to use a writer that writes bytes directly to the memory pointed to by out
.
Perhaps this?
const N = 10000 // should be sizeof(*out) or larger
buf := bytes.NewBuffer((*[N]byte)(unsafe.Pointer(out))[:])
This makes a bytes.Buffer
that directly writes to the out
struct without going through any intermediate memory. Note that because of unsafe shenanigans, this is vulnerable to a buffer overflow if you write more bytes of data than is pointed to by out
.
Words of warning: this is all pretty nasty, and prone to the same sorts of problems you'd find in C, and you'd need to check the cgo pointer rules to make sure that you're not vulnerable to garbage collection interactions. A point of advice: given that you say you "don't have much experience with pointers and memory allocation", you probably should avoid writing or including code like this because the problems it can introduce are nefarious and may not be immediately obvious.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论