【问题标题】:c reinterpret pointer to datatype with bigger sizec重新解释指向更大尺寸数据类型的指针
【发布时间】:2015-02-26 21:29:53
【问题描述】:

我正在尝试解释通过 TCP 连接获得的 WebSocket 帧。我想在纯 C 中做到这一点(所以没有 reinterpret_cast)。格式在IEEE RFC 6455 中指定。我想填写以下结构:

typedef struct {
    uint8_t flags;
    uint8_t opcode;
    uint8_t isMasked;
    uint64_t payloadLength;
    uint32_t maskingKey;
    char* payloadData;
} WSFrame;

具有以下功能:

static void parseWsFrame(char *data, WSFrame *frame) {
    frame->flags = (*data) & FLAGS_MASK;
    frame->opcode = (*data) & OPCODE_MASK;
    //next byte
    data += 1;
    frame->isMasked = (*data) & IS_MASKED;
    frame->payloadLength = (*data) & PAYLOAD_MASK;

    //next byte
    data += 1;

    if (frame->payloadLength == 126) {
        frame->payloadLength = *((uint16_t *)data);
        data += 2;
    } else if (frame->payloadLength == 127) {
        frame->payloadLength = *((uint64_t *)data);
        data += 8;
    }

    if (frame->isMasked) {
        frame->maskingKey = *((uint32_t *)data);
        data += 4;
    }else{
        //still need to initialize it to shut up the compiler
        frame->maskingKey = 0;
    }
    frame->payloadData = data;
}

代码适用于 ESP8266,因此只能使用 printfs 到串行控制台进行调试。使用这种方法,我发现代码在frame->maskingKey = *((uint32_t *)data); 之后立即崩溃,并且前两个 if 被跳过,所以这是我第一次将指针转换为另一个指针。

数据没有\0 终止,但我在接收到的数据回调中获得了大小。在我的测试中,我试图通过已经建立的WebSocket发送消息'test',并且接收到的数据长度是10,所以:

  • 1 字节标志和操作码
  • 1 字节掩码和有效负载长度
  • 4 字节掩码键
  • 4 字节有效载荷长度

在代码崩溃时,我希望数据从初始位置偏移 2 个字节,因此它有足够的数据来读取以下 4 个字节。

我很长时间没有编写任何 C 代码,所以我预计我的代码中只会出现一个小错误。

PS.:我见过很多代码,它们逐字节解释值并移动值,但我看不出为什么这种方法也不起作用。

【问题讨论】:

  • 根据您提供的信息很难确定,但对于 32 位取消引用,数据指针似乎没有正确对齐。尝试一次构造一个字节的 maskingKey。在您的所有代码中,取消引用数据作为字节指针。例如。 ((uint8_t*)data)[0]
  • IEEE RFC 6455 参考中的图片显示掩码键未在 32 位边界上对齐,因此未对齐的访问可能导致崩溃。
  • 您的 payloadLengthmaskingKeypayloadData 在该结构中未正确对齐。您可以在上面使用#pragma pack,但是在使用它们之前,您必须将这些变量memcpy 复制到本地副本中。更好的是,只需重新定义您的协议,并在这些字段之前添加 uint8_t reserved
  • & 开头的行有点可疑,“操作码”和“长度”等值更常见的是基于 LSB 而不是将它们保持在它们的任何位位置进去了。

标签: c casting pointer-arithmetic


【解决方案1】:

将 char* 转换为指向更大类型的指针的问题在于,某些架构不允许未对齐的读取。

也就是说,例如,如果您尝试通过指针读取 uint32_t,则指针本身的值必须是 4 的倍数。否则,在某些架构上,您会遇到总线故障(例如 -信号、陷阱、异常等)。

因为这些数据是通过 TCP 传入的,并且流/协议的格式没有任何填充,所以您可能需要将其从缓冲区中逐字节读取到局部变量中(例如 - 使用 memcpy)作为适当的。例如:

if (frame->isMasked) {
    mempcy(&frame->maskingKey, data, 4);
    data += 4;
    // TODO: handle endianness: e.g.: frame->maskingKey = ntohl(frame->maskingKey);
}else{
    //still need to initialize it to shut up the compiler
    frame->maskingKey = 0;
}

【讨论】:

  • 那太糟糕了。我当然听说过对齐读取故障螺母从未遇到任何具体问题。感谢您的回答! ntohl 似乎不在 c lib 中,所以我使用带有单个数组读取和移位的方法
【解决方案2】:

有两个问题:

  • data 可能未正确对齐 uint32_t
  • data 中的字节顺序可能与您的硬件用于整数值表示的顺序不同。 (有时称为“字节序问题”)。

要编写可靠的代码,请查看消息规范以了解字节的输入顺序。如果它们是最重要的字节,那么您的代码的可移植版本将是:

unsigned char *udata = (unsigned char *)data;
frame->maskingKey = udata[0] * 0x1000000ul
                  + udata[1] * 0x10000ul
                  + udata[2] * 0x100ul
                  + udata[3];

一开始这可能看起来很少,但您可以创建一个内联函数,将指针作为参数,并返回 uint32_t,这将使您的代码保持可读性。

类似的问题也适用于您对uint16_t 的读取。

【讨论】:

    猜你喜欢
    • 2018-01-03
    • 1970-01-01
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 2014-08-14
    • 2018-11-23
    • 1970-01-01
    相关资源
    最近更新 更多