这与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
指针来遍历缓冲区。
编译器将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 ----------------+
这会发生什么?:
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
,上述陈述会做什么?
答案 0 :(得分:2)
关于你的第二个问题,一个地址只是一个整数,只是编译器使用它来表示一个内存位置。表达式((uintptr_t)dp & 1)
首先将其转换为正确的整数(类型uintptr_t
是一个足以容纳指针的整数),并检查是否设置了最低有效位。如果未设置该位,则表达式的结果为零,这意味着该地址是偶数且16位对齐。如果 位设置为意味着地址不均匀且不是16位对齐。
这个表达式的有趣之处在于它将导致0
或1
成为结果,具体取决于该位是否未设置或是否为。如果未设置该位,则0
将添加到已经为16位的对齐地址,从而不会发生任何变化。另一方面,如果地址不是16位对齐,则表达式会导致1
被添加到地址,自动将其对齐到16位边界。
答案 1 :(得分:1)
dp += ((uintptr_t)dp & 1);
将dp
舍入到2的下一个倍数。如果dp
已经是偶数,则该语句无效。答案 2 :(得分:1)
要解决您的第一个问题,dest
指向要写入的内存。 void *
告诉你没有声明特定类型,它只是一块内存。 switch
块测试预期的内容,并将指针递增一个字节或两个字节。这样做的方式是安全的,因为unsigned char
保证在sizeof(char)
处对齐,即1
。
编辑:对此不安全的是dest
在这种情况下指向libusb_endpoint_descriptor
,假设其对齐方式以descriptor
表示。这些假设取决于无法保证的填充预期。可能这里的代码依赖于编译器选项进行打包。