使用指针在struct中的元素之间移动

时间:2013-05-23 13:44:21

标签: c memory struct

这与this question密切相关。

我正在使用libusb编写一些USB代码。查看库的源代码,我看到他们正在使用指针来解析数据并填充structs

示例:

来自libusb.h

struct libusb_endpoint_descriptor {
    uint8_t  bLength;
    uint8_t  bDescriptorType;
    uint8_t  bEndpointAddress;
    uint8_t  bmAttributes;
    uint16_t wMaxPacketSize;
    uint8_t  bInterval;
    uint8_t  bRefresh;
    uint8_t  bSynchAddress;
    const unsigned char *extra;
    int extra_length;
};

来自descriptor.c

struct libusb_endpoint_descriptor *endpoint
unsigned char *buffer
int host_endian

usbi_parse_descriptor(buffer, "bbbbwbbb", endpoint, host_endian);

其中usbi_parse_descriptor是:

int usbi_parse_descriptor(
        unsigned char *source, 
        const char *descriptor,
        void *dest, 
        int host_endian)
{
    unsigned char *sp = source;
    unsigned char *dp = dest;
    uint16_t w;
    const char *cp;

    for (cp = descriptor; *cp; cp++) {
        switch (*cp) {
        case 'b':    /* 8-bit byte */
                *dp++ = *sp++;
                break;
        case 'w':    /* 16-bit word, convert from little endian to CPU */
            dp += ((uintptr_t)dp & 1);    /* Align to word boundary */
            if (host_endian) {
                memcpy(dp, sp, 2);
            } else {
                w = (sp[1] << 8) | sp[0];
                *((uint16_t *)dp) = w;
            }
            sp += 2;
            dp += 2;
            break;
        }
    }

    return (int) (sp - source);
}

我的问题是使用char指针来遍历缓冲区。

  1. 编译器将uint8_t对齐为一个例如uint32_t整数的风险是否有可能 - 因此*dp++最终会出现错误的地址?
    错误的地址我的意思是libusb_endpoint_descriptor指向的结构dp中变量的地址:

    unsigned char *buffer = REPLY from request to USB device.
    struct libusb_endpoint_descriptor *endpoint;
    unsigned char *dp = (void*) endpoint;
    
      *dp = buffer[0] ==> struct libusb_endpoint_descriptor -> bLength
    *++dp = buffer[1] ==> struct libusb_endpoint_descriptor -> bDescriptorType
    ... v                                                         ^
        |                                                         |
        +--- does this guaranteed align with this ----------------+
    
  2. 这会发生什么?:

    dp += ((uintptr_t)dp & 1);    /* Align to word boundary */
    
  3. 如果结构在内存中是这样的:

    ADDRESS  TYPE      NAME
    0x000a0  uint8_t   var1
    0x000a1  uint16_t  var2
    0x000a3  uint8_t   var3
    

    dp指向var1; 0x000a0,上述陈述会做什么?

3 个答案:

答案 0 :(得分:2)

关于你的第二个问题,一个地址只是一个整数,只是编译器使用它来表示一个内存位置。表达式((uintptr_t)dp & 1)首先将其转换为正确的整数(类型uintptr_t是一个足以容纳指针的整数),并检查是否设置了最低有效位。如果未设置该位,则表达式的结果为零,这意味着该地址是偶数且16位对齐。如果 位设置为意味着地址不均匀且不是16位对齐。

这个表达式的有趣之处在于它将导致01成为结果,具体取决于该位是否未设置或是否为。如果未设置该位,则0将添加到已经为16位的对齐地址,从而不会发生任何变化。另一方面,如果地址不是16位对齐,则表达式会导致1被添加到地址,自动将其对齐到16位边界。

答案 1 :(得分:1)

  1. 不确定,我不打赌保证它的标准,但至少在大多数PC平台上都应该有效。
  2. dp += ((uintptr_t)dp & 1);dp舍入到2的下一个倍数。如果dp已经是偶数,则该语句无效。

答案 2 :(得分:1)

要解决您的第一个问题,dest指向要写入的内存。 void *告诉你没有声明特定类型,它只是一块内存。 switch块测试预期的内容,并将指针递增一个字节或两个字节。这样做的方式是安全的,因为unsigned char保证在sizeof(char)处对齐,即1

编辑:对此不安全的是dest在这种情况下指向libusb_endpoint_descriptor,假设其对齐方式以descriptor表示。这些假设取决于无法保证的填充预期。可能这里的代码依赖于编译器选项进行打包。