go - 等价于 C++ reinterpret_cast a void* to a struct in Golang

标签 go

在 C++ 中,您可以从 FILE 描述符中读取数据,然后简单地将其重新解释为结构以解释数据。

在 Go 中是否有等效的方法来执行此操作?

作为一个非常人为的示例,请考虑以下内容,其中“ProcessBytes”只是一个回调,其中为您提供了一个字节数组,当从文件中读取时,这些字节数组会不断追加。

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
  }
}

最佳答案

现在的答案是建议使用 unsafe,但它们并没有讨论为什么你不应该使用 unsafe 以及你应该做什么。所以让我试一试。

在您在 OP 中发布的 C++ 代码中,您似乎在编写一种二进制格式,它通过简单的强制转换读取数据。简单,但有效。我能看到的唯一明显的问题是它不允许 Little Endian 和 Big Endian 之间的互操作性,但这是另一回事。

在 Go 中处理二进制编码的方法是使用方便的包 encoding/binary,它能够将二进制数据直接解码为固定大小的结构(即没有字符串或 slice ,它们是可变长度的,因此长度需要任意编码)。

以下是我将如何在 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\nMessage: %s\n",
            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

所以,这里有几点需要注意:

  • 在 Go 中,二进制格式通常从不直接从内存中读取。这是因为 1. 它依赖于平台(Little/Big endian),2. 字符串、 slice 和结构填充有问题,以及 3. 嗯,不安全。如果你不直接篡改内存,Go 几乎可以保证你的程序在任何平台上都可以流畅运行而无需任何修改。一旦你开始这样做,你就失去了这种保证。
  • 我们不需要“推进指针”我们正在读取的数据 - 我们正在传递给 binary.Readio.Reader,这意味着当我们从中读取某些内容时,读取的数据将被丢弃,因此指针会自动前进。
  • 当你自己玩内存时,GC 可能会产生影响 - GC 可能认为数据中的某个点不再被引用并且可以自由使用 - 而实际上你仍在使用它,只是没有清楚地引用使用 native Go 指针。

关于go - 等价于 C++ reinterpret_cast a void* to a struct in Golang,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52547305/

相关文章:

go - 为什么追加不适用于 slice ?

selenium - Go Lang 中的自动化 - 如何使用像 Selenium 这样的浏览器自动化?

go - 从zip.NewWriter返回io.ReadCloser

function - 类型如何间接引用其他函数基方法?

go - 在函数调用中传递带有参数的类型

Gocql 自定义编码器

go - 为什么 IsValid() 对 int 的零值返回 true?

go - 如何使用 GoLang 使用 Google PubSub opencensus 指标?

sockets - 使用 golang 将 JSON 发送到 unix 套接字

go - 当我的客户端连接时,简单的 RPC 服务器不会响应