如何在go中正确使用系统调用(Go unsafe.Sizeof vs C sizeof的结果不同)

问题描述 投票:2回答:2

Go的unsafe.Sizeof返回的结果与C的sizeof不同。

main.go:

package main

import (
    "unsafe"
)

type gpioeventdata struct {
    Timestamp uint64
    ID        uint32
}

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

在macOS上使用12编译时打印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定义:

struct gpioevent_data {
    __u64 timestamp;
    __u32 id;
};

编辑

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

如果这是错的,那么调用系统调用的正确方法是什么,以便能够正确使用不同的体系结构。例如GPIO ioctl调用:

ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
...
struct gpioevent_data event;
ret = read(req.fd, &event, sizeof(event));
go struct sizeof
2个回答
2
投票

go编译器和C编译器以不同方式处理对齐。

在C中,结构已经对齐到16个字节(在id之后或之前添加了4个字节的松弛空间)。 go编译器改为填充字段而不添加任何松弛空间。

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

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

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


0
投票

根据https://go101.org/article/memory-layout.html,go通常遵循结构填充的C规则(有关C存储器对齐规则的详细信息,请参阅https://stackoverflow.com/a/38144117/851737,对于伪代码的算法,请参阅here)。

但是,有一个known bug无法正确对齐32位架构上的64位值。

© www.soinside.com 2019 - 2024. All rights reserved.