如何在Go中正确使用系统调用(Go不安全的不同结果。Sizeof与C sizeof)

Go's unsafe.Sizeof is returning a different result than 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))
}

Prints 12 when compiled with env GOOS=linux GOARCH=arm GOARM=6 go build on macOS and run on Raspberry Pi Zero.

gpio.c:

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

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

Prints 16 when compiled and run on Raspberry (with gcc).

struct definition in gpio.h:

struct gpioevent_data {
    __u64 timestamp;
    __u32 id;
};

Edit

I already thought that this is due to alignment, but a lot of people are passing Go structs to syscall.Syscall (e.g. https://github.com/stapelberg/hmgo/blob/master/internal/gpio/reset.go#L49). So that's basically wrong and you should never do that?

If that's wrong, what would be the correct approach calling syscalls with go so that works correctly with different architectures. For example GPIO ioctl calls:

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

The go compiler and the C compiler are handling alignment differently.

In C the structure has been aligned to 16 bytes (adding a 4 bytes slack space after id or before it). The go compiler instead packed the fields without adding any slack space.

Your mistake is thinking that two "structures" in different languages with different compilers should have the same memory layout.

Note that there is no way to "compute" what will be the padding in a C or C++ structure because padding is a choice of the implementer. It's well possible that two different standard-conforming C compilers for the same architecture will generate different paddings (or even the same compiler with different compiling options).

The only way to get the padding correct is to check the specific case, either manually or by writing a script that calls the compiler passing the same compiling options and checks the result (e.g. by output the results of offsetof on all the members) and then generates the needed go source code after parsing that output.

According to https://go101.org/article/memory-layout.html, go generally follows the C rules for structure padding (see https://stackoverflow.com/a/38144117/851737 for details of the C memory alignment rules and here for an algorithm in pseudocode).

However, there is a known bug that go doesn't align 64bit values on 32bit architectures correctly.