【问题标题】:Using pointers to move between elements in struct使用指针在结构中的元素之间移动
【发布时间】:2013-05-23 13:44:21
【问题描述】:

这与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++ 以错误的地址结束,这难道不是一种风险吗?
    错误地址是指dp指向的struct libusb_endpoint_descriptor中变量的地址:

    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 */
    

如果内存中的结构是这样的:

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

和dp 指向var1; 0x000a0,上面的语句会做什么?

【问题讨论】:

  • 部分答案:dp += ((uintptr_t)dp & 1); /* 对齐字边界 */ 将 dp 视为指针的整数表示(使用机器上的适当大小来表示指针)。如果指针指向一个奇数地址,它会将 1 添加到 dp。这样做会使 dp 在 16 位边界上对齐。继续,如果 dp 指向 var1,则该语句保持 dp 不变。如果它指向 &var2,则语句将 dp 移动到 &var3
  • 实际上是将dp移动到var2的第二个字节,而不是var3(var2未对齐)。

标签: c memory struct


【解决方案1】:

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

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

【讨论】:

  • 所以uint16_t总是与16位地址对齐?这是有保证的吗?或者换一种说法——这是安全的代码吗?
  • @Zimzalabim 不,不能保证 16 位变量与 16 位边界对齐,这意味着您显示的代码具有潜在危险。但是,除非要求不,否则我所知道的所有编译器(旧的和新的)都会将大于 8 位的变量与字边界对齐,因为它通常会加快访问速度。此外,结构本身确保 16 位变量放置在正确的边界上。
  • 可能是这段代码处理了一些要求某种对齐的 USB 规范,在这种情况下代码是安全的,不是吗?担心的是unsigned char 的对齐,但我认为保证是1。
  • @s.bandara:USB 规范指定了各种类型的请求,例如控制管道。这里是9.4.3 Get descriptorpp 253 of USB 2.0——它返回一个 8 位缓冲区,其中包含第 269 页表 9-13 中指定的数据。
  • @JoachimPileborg:字边界我假设您的意思是 AX、CX、DX 等寄存器的大小?与 实模式 一样,(通常为 16 位),最大尺寸。它隐含在您所写内容的上下文中,但仅要求确定。 64 位环境也是这样吗?
【解决方案2】:
  1. 不确定,我不会打赌保证它的标准,但至少在大多数 PC 平台上它应该可以工作。
  2. dp += ((uintptr_t)dp &amp; 1); 将 dp 舍入到下一个 2 的倍数。如果 dp 已经是偶数,则该语句无效。

【讨论】:

  • 关于pt。 1. 如果他们使用可能有用的东西,我觉得有点奇怪。我想知道自己是否可以做类似的事情,但前提是我可以说它做工作。
  • 我认为对齐,而不是未定义,是实现定义的。因此,如果您的平台规范说它有效,它确实有效。
  • 谢谢。是的——这就是我停下来的地方。我试图在支持/确认这一点的库中找到一些编译器标志、cmets 或其他,但没有运气。因为它是适用于 Linux、BSD、OS X (Darwin) 和 Windows 的库。对我来说,它变成了一个两部分的事物。 1.我如何自己安全地使用它,而不是受平台限制(如果有的话)。 2. libusb 是一个不错的选择,还是我应该去别处看看。我想我得继续读下去了。
【解决方案3】:

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

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

【讨论】:

  • 是的,我明白了,但我关心的是目标。如在例如dp-&gt;bInterval。如果那个是对齐的。
  • 我明白了。这可能仅适用于对无法保证的填充进行假设。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-04
  • 2023-02-16
  • 1970-01-01
  • 2019-03-22
  • 2020-11-05
  • 1970-01-01
相关资源
最近更新 更多