首页 > 解决方案 > 如何在 go 中正确使用系统调用(Go unsafe.Sizeof 与 C sizeof 的结果不同)

问题描述

Go'sunsafe.Sizeof返回的结果与 C's 不同sizeof

main.go:

package main

import (
    "unsafe"
)

type gpioeventdata struct {
    Timestamp uint64
    ID        uint32
}

func main() {
    eventdata := gpioeventdata{}
    println("Size", unsafe.Sizeof(eventdata))
}

12在 macOS 上编译env GOOS=linux GOARCH=arm GOARM=6 go build并在 Raspberry Pi Zero 上运行时打印。

gpio.c:

#include <stdio.h>
#include <linux/gpio.h>

int main() {    
    printf("sizeof gpioevent_data %zu\n", sizeof(struct gpioevent_data));
}

16在 Raspberry 上编译和运行时打印(带有gcc)。

gpio.h 中的结构定义:

struct gpioevent_data {
    __u64 timestamp;
    __u32 id;
};

编辑

我已经认为这是由于对齐,但是很多人将 Go 结构传递给syscall.Syscall(例如https://github.com/stapelberg/hmgo/blob/master/internal/gpio/reset.go#L49)。所以这基本上是错误的,你不应该这样做吗?

如果那是错误的,那么使用 go 调用系统调用的正确方法是什么,以便在不同的体系结构中正常工作。例如 GPIO ioctl 调用:

ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
...
struct gpioevent_data event;
ret = read(req.fd, &event, sizeof(event));

标签: gostructsizeof

解决方案


编译器goC编译器处理对齐方式不同。

在 C 中,该结构已对齐为 16 字节(在其之后id或之前添加 4 字节的松弛空间)。go编译器反而在不添加任何松弛空间的情况下打包字段。

您的错误是认为具有不同编译器的不同语言的两个“结构”应该具有相同的内存布局。

请注意,没有办法“计算”C 或 C++ 结构中的填充,因为填充是实现者的选择。相同架构的两个不同的符合标准的 C 编译器很可能会生成不同的填充(甚至是具有不同编译选项的相同编译器)。

使填充正确的唯一方法是检查特定情况,手动或通过编写调用编译器的脚本传递相同的编译选项并检查结果(例如,通过输出offsetof所有成员的结果)然后生成解析该输出后所需的go源代码。


推荐阅读