Реализую сетевой пакет в голанге. Это уже было реализовано на C++. Цель состоит в том, чтобы заставить клиент, реализованный в golang, взаимодействовать с сервером, реализованным на C++.
Они будут общаться по пакету. Структура пакета:
type Packet struct {
length uint32
nameLen uint8
data []byte
} // in golang
struct Packet {
uint32_t length;
uint8_t nameLen;
byte data[];
} // in C++
Их подчеркнутая структура представляет собой массив байтов. При получении сообщения в формате байтового массива. Нам нужно перевести это в Packet.
auto p = reinterpret_cast<Packet*>(buffer); // in c++
(buffer's alignment is manually set as 64)
p := (Packet)(unsafe.Pointer(&buffer)) // in golang
Чтобы они могли общаться, их структура должна оставаться неизменной.
Возникает вопрос: Распечатав их выравнивание, я получаю следующее:
type Packet struct {
length uint32 // alignment 8
nameLen uint8 // alignment 8
data []byte // alignment 8
}
struct Packet {
uint32_t length; // alignment 4
uint8 nameLen; // alignment 4
data []byte; // alignment 1
}
Они будут декодировать сообщение по-разному из-за разного выравнивания.
Не могу изменить код C++.
Q1: Есть ли способ настроить выравнивание полей структуры в golang?
Q2: Есть ли лучший способ реализовать пакет golang, чтобы избежать несоответствия выравнивания при преобразовании буфера в пакет?
Q1: Нет. Q2: Да: Занимайтесь программированием вместо магии.
Спасибо вам, ребята. Распаковываю поля по одному.

Согласно комментарию Адриана и Волкера,
Q1: Нет
Q2: Поупражняйтесь в программировании, чтобы распаковывать поля по отдельности.
В пакете кодирование / двоичный у нас есть
func PutUVarint
Он кодирует uint64 в buf и возвращает количество записанных байтов. Почему-то в этом пакете нет общедоступной функции для кодирования uint32. Итак, я сделал нечто подобное:
func PutUint32(b []byte, v uint32) {
_ = b[3] // early check
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
} // assume it's littleEndian
// to store packet length into buffer.
PutUint32(buffer, pkt.length)
Вы можете сделать то же самое, что и c / C++, но небезопасно.
Просто используйте массив байтов фиксированной ширины увеличенного размера.
С учетом сказанного вам нужно сделать свою собственную проверку границ.
package main
import (
"unsafe"
"runtime"
"reflect"
"fmt"
)
type Packet struct {
length uint32 // alignment 4
nameLen uint8 // alignment 1
data [2048]byte // alignment 1
}
func NewPacketFromBuffer(buffer []byte) *Packet {
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buffer))
packet := (*Packet)(unsafe.Pointer(sh.Data))
runtime.KeepAlive(buffer) // So the buffer will not be garbage collected
return packet
}
func main () {
buffer := []byte{9,0,0,0,4,'d','a','t','a'};
p := NewPacketFromBuffer(buffer)
fmt.Printf("length : %d, nameLen : %d, name: %s\n", p.length,
p.nameLen, p.data[0:p.nameLen] )
}
Q2: Очень, очень да. Распаковывайте поля индивидуально, используя
encoding/binary, вместо того, чтобы рассчитывать на распаковку всего этого сразу на разных языках и архитектурах.