Как правильно использовать системные вызовы в go (отличные результаты от Go unsafe.Sizeof против C sizeof) - PullRequest
2 голосов
/ 13 апреля 2019

Go's 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))
}

Печатает 12 при компиляции с env GOOS=linux GOARCH=arm GOARM=6 go build в macOS и работает на 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));

Ответы [ 2 ]

2 голосов
/ 13 апреля 2019

Компилятор go и компилятор C обрабатывают выравнивание по-разному.

В C структура была выровнена до 16 байтов (добавление 4-байтового пробела после id или перед ним). Вместо этого компилятор go упаковал поля без добавления свободного места.

Ваша ошибка в том, что две "структуры" на разных языках с разными компиляторами должны иметь одинаковую структуру памяти.

Обратите внимание, что нет способа "вычислить", что будет заполнением в структуре C или C ++, потому что заполнение является выбором реализатора. Вполне возможно, что два разных стандартных C-компилятора для одной и той же архитектуры будут генерировать разные отступы (или даже один и тот же компилятор с разными параметрами компиляции).

Единственный способ получить правильное заполнение - это проверить конкретный случай, либо вручную, либо написав скрипт, который вызывает компилятор, передавая те же параметры компиляции, и проверяет результат (например, выводя результаты offsetof на всех члены), а затем генерирует необходимый исходный код go после анализа этого вывода.

0 голосов
/ 16 апреля 2019

Согласно https://go101.org/article/memory-layout.html, go обычно следует правилам C для заполнения структуры (подробности о правилах выравнивания памяти в C см. В https://stackoverflow.com/a/38144117/851737 и здесь для алгоритма в псевдокоде).

Однако существует известная ошибка , которая приводит к неправильному выравниванию 64-битных значений на 32-битных архитектурах.

...