等效于C ++ reinterpret_cast void *到Golang中的结构

In C++ you can read in data from a FILE descriptor and simply reinterpret_cast it into a structure to interpret the data.

Is there an equivalent way to do this in Go?

As a very contrived example, consider the following where "ProcessBytes" is simply a callback in which you are given an array of bytes that are continuously appended to when reading from a file.

struct PayloadHeader {
  uint32_t TotalPayloadLength; 
  uint8_t  PayloadType;
};

struct TextMessage {
  PayloadHeader Header;
  uint32_t      SenderId;
  uint32_t      RecieverId;
  char          Text[64]; // null padded
};

void ProcessBytes(const uint8_t* data, size_t dataLength) {
  if(dataLength < sizeof(PayloadHeader))
    return;

  const PayloadHeader* header = reinterpret_cast<const PayloadHeader*>(data);
  if(header.PayloadType == TEXT_MESSAGE) {
    if(header.TotalLength != sizeof(TextMessage))
      return;
    const TextMessage* text = reinterpret_cast<const TextMessage*>(data);
    // Do something with the text message~

    // Adjust the *data* to 'erase' the bytes after we are done processing it
    // as a TextMessage
  }
}

Right now answers are suggesting unsafe, but they are not going over why you should not use unsafe and what you should do instead. So let me give it a try.

In the C++ code you posted in OP, you seem to be writing a sort of binary format, which reads data through simple casting. Simple, but effective. The only obvious problem I can see is that it does not allow interoperability between Little Endian and Big Endian, but that's another matter.

The way you would approach binary encoding in Go is through the use of the handy package encoding/binary, which is able to decode binary data directly into fixed-size structs (ie. without strings or slices, which are variable-length and so the length would need to be encoded arbitrarily).

Here is how I would implement your example in Go:

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
)

const textMessage = 11

func main() {
    // Set up our data - this is an example of the data I
    // imagine you want to decode.
    r := bytes.NewReader([]byte{
        // Header
        byte(textMessageLen + headerLen), 0, 0, 0,
        11,

        // Body
        137, 0, 0, 0,
        117, 0, 0, 0,
        // Message content
        'H', 'e', 'l', 'l', 'o', '!', 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 
        0, 0, 0, 0, 0, 0, 0, 0, 
        0, 0, 0, 0, 0, 0, 0, 0, 
        0, 0, 0, 0, 0, 0, 0, 0, 
        0, 0, 0, 0, 0, 0, 0, 0, 
        0, 0, 0, 0, 0, 0, 0, 0, 
        0, 0, 0, 0, 0, 0, 0, 0, 
    })

    // We first read the header to decide what to do next.
    // Notice that we explicitly pass an argument that indicates to
    // parse integers using little endian, making this code portable.
    var h Header
    err := binary.Read(r, binary.LittleEndian, &h)
    if err != nil {
        fmt.Println(err)
        return
    }


    switch h.Type {
    case textMessage:
        // It's a text message - make sure the length is right.
        if textMessageLen != (int(h.Length) - headerLen) {
            fmt.Println("Invalid payload length")
            return
        }

        // Decode the data
        var t TextMessage
        err = binary.Read(r, binary.LittleEndian, &t)
        if err != nil {
            fmt.Println(err)
            return
        }

        // Print it out
        fmt.Printf("Sender: %d; Receiver: %d
Message: %s
",
            t.Sender, t.Receiver, bytes.TrimRight(t.Text[:], "\x00"))
    default:
        fmt.Println("unknown payload type")
    }
}

// If you need to find out what the encoded size of a struct is, don't use unsafe.Sizeof;
// use binary.Size instead.
var headerLen = binary.Size(Header{})

type Header struct {
    Length uint32
    Type   uint8
}

var textMessageLen = binary.Size(TextMessage{})

type TextMessage struct {
    Sender, Receiver uint32
    Text             [64]byte
}

Playground

So, here are a few things to note:

  • In Go, binary formats are usually NEVER implemented reading directly from memory. This is because 1. it's platform-dependent (Little/Big endian), 2. trouble with strings, slices and struct paddings and 3. it's, well, unsafe. If you don't tamper with memory directly, Go can pretty much guarantee that your program will run smoothly on any platform without any modifications. The moment you start doing that, you lose that guarantee.
  • We don't need to "advance the pointer" of the data we're reading - we're passing down to binary.Read an io.Reader, which means that when we read something from it the data read is discarded, so the pointer is automatically advanced.
  • There are possible implications with the GC when playing with memory with yourself - the GC might think that a point in the data is no longer referenced and free to be used - whereas really you're still using it, just not clearly referencing using a native Go pointer.

You can, but you should not because it is unsafe. Golang not define the memory model(as I known) of the struct, it may change between golang versions. Do marshal/unmarshal is the regular way.

package main

import (
    "fmt"
    "unsafe"
)

type mys struct{
    a int
}

func main() {
    v := mys{0x1234abcd}
    unsafeArrayP := (*[unsafe.Sizeof(v)]byte)(unsafe.Pointer(&v))
    for i:=0;i<4;i++ {
        fmt.Printf("%x,", unsafeArrayP[i])
    }

    v2 := 0x1234abcd
    v3 := *((*mys)(unsafe.Pointer(&v2)))
    fmt.Println(v == v3)
}

prints cd,ab,34,12,true