c重新解释指向更大尺寸的数据类型的指针

时间:2015-02-26 21:29:53

标签: c casting pointer-arithmetic

我正在尝试解释通过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);之后崩溃,前两个ifs被跳过,所以这是我第一次将指针转换为另一个指针。

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

  • 1字节标志和操作码
  • 屏蔽1个字节且有效负载长度
  • 4字节屏蔽密钥
  • 4字节有效负载长度

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

我很长时间没有对任何C进行编码,所以我希望代码中只有一个小错误。

PS:我看过很多代码,他们逐字节地解释这些值并移动值,但我认为没有理由说这个方法也不适用。

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:0)

有两个问题:

  • 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的读取。