代码之家  ›  专栏  ›  技术社区  ›  Chris Nelson

从C访问网络数据包中未对齐数据的安全、高效方法

  •  4
  • Chris Nelson  · 技术社区  · 16 年前

    我在ARM9处理器上用C语言编写Linux程序。该程序用于访问包含一系列标记数据的网络数据包,如:

    <fieldID><length><data><fieldID><length><data> ...
    

    fieldID和length字段都是uint16,数据可以是1个或更多字节(如果使用的是全长,则高达64k,但不是)。

    只要 <data> 有偶数个字节,我看没有问题。但是如果我有一个1或3或5字节 <数据> 然后下一个16位fieldID不在16位边界上,我预计会出现对齐问题。我已经有一段时间没有做过这样的事情了,所以我对细节有点不确定。欢迎反馈。谢谢。

    4 回复  |  直到 16 年前
        1
  •  6
  •   Greg Hewgill    16 年前

    为了避免这种情况下的对齐问题,请作为 unsigned char * . 所以:

    unsigned char *p;
    //...
    uint16_t id = p[0] | (p[1] << 8);
    p += 2;
    

    上面的例子假设了“小端”数据布局,在多字节数中,最低有效字节排在第一位。

        2
  •  4
  •   osgx    13 年前

    您应该有函数(如果您使用的语言支持这些特性,则为内联函数和/或模板函数),这些函数将读取可能未对齐的数据并返回您感兴趣的数据类型。类似于:

    uint16_t unaligned_uint16( void* p)
    {
        // this assumes big-endian values in data stream
        //  (which is common, but not universal in network
        //  communications) - this may or may not be 
        //  appropriate in your case
    
        unsigned char* pByte = (unsigned char*) p;
    
        uint16_t val = (pByte[0] << 8) | pByte[1];
    
        return val;
    }
    
        3
  •  4
  •   Adam Rosenfield    13 年前

    简单的方法是手动重建 uint16_t s、 以牺牲速度为代价:

    uint8_t *packet = ...;
    uint16_t fieldID = (packet[0] << 8) | packet[1];  // assumes big-endian host order
    uint16_t length = (packet[2] << 8) | packet[2];
    uint8_t *data = packet + 4;
    packet += 4 + length;
    

    如果你的处理器支持它,你可以输入pun或使用union(但是要注意 strict aliasing ).

    uint16_t fieldID = htons(*(uint16_t *)packet);
    uint16_t length = htons(*(uint16_t *)(packet + 2));
    

    请注意,不总是支持未对齐的访问(例如,它们可能会生成某种类型的错误),在其他体系结构上,也支持它们,但会有性能损失。

    如果数据包未对齐,则始终可以将其复制到静态缓冲区中,然后读取:

    static char static_buffer[65540];
    memcpy(static_buffer, packet, packet_size);  // make sure packet_size <= 65540
    uint16_t fieldId = htons(*(uint16_t *)static_buffer);
    uint16_t length = htons(*(uint16_t *)(static_buffer + 2));
    

    就我个人而言,我会选择1号,因为它是最便携的。

        4
  •  1
  •   Joe    16 年前

    如果通过一个字节指针,对齐总是很好的,尽管可能不是超级高效的。

    抛开尾数问题不谈,您可以将“real”字节指针存储到您想要/需要的、正确对齐的任何内容中,这样就可以了。

    (这是因为生成的代码将以字节的形式加载/存储数据,这是对齐安全的)。当生成的程序集有以错误的方式加载和存储16/32/64位内存的指令时,它就会崩溃。