这个问题不是关于未对齐数据访问的定义,而是为什么尽管生成相同的程序集代码,memcpy却会沉默UBsan发现而类型转换不会。
我有一些示例代码来解析一个协议,该协议将一个字节数组分割成6个字节组。
void f(u8 *ba) {
// I know this array's length is a multiple of 6
u8 *p = ba;
u32 a = *(u32 *)p;
printf("a = %d\n", a);
p += 4;
u16 b = *(u16 *)p;
printf("b = %d\n", b);
p += 2;
a = *(u32 *)p;
printf("a = %d\n", a);
p += 4;
b = *(u16 *)p;
printf("b = %d\n", b);
}在将我的指针增加6并进行另一次32位读取之后,UBSan报告了一个关于对齐负载的错误。我使用memcpy而不是类型双关语来抑制这个错误,但我不太清楚原因。要明确的是,这里是相同的例程,没有UBSan错误,
void f(u8 *ba) {
// I know this array's length is a multiple of 6 (
u8 *p = ba;
u32 a;
memcpy(&a, p, 4);
printf("a = %d\n", a);
p += 4;
memcpy(&b, p, 2);
printf("b = %d\n", b);
p += 2;
memcpy(&a, p, 4);
printf("a = %d\n", a);
p += 4;
memcpy(&b, p, 2);
printf("b = %d\n", b);
}这两个例程编译成相同的汇编代码(32位读取使用movl,16位读取使用movzwl ),那么为什么一种未定义的行为不是呢?memcpy有一些特殊的属性来保证某些东西吗?
我不想在这里使用memcpy,因为我不能依赖编译器对其进行足够好的优化。
发布于 2017-12-03 19:26:52
UB杀菌剂用于检测代码是否严格一致,实际上取决于未定义的行为,而这些行为没有得到保证。
实际上,C标准说,这种行为是未定义的,只要,您就可以转换一个指向地址不对齐的类型的指针。C11 (草案,n1570) 6.3.2.3p7
指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针不能正确地对齐引用类型的68),则未定义行为。
也就是说。
u8 *p = ba;
u32 *a = (u32 *)p; // undefined behaviour if misaligned. No dereference required这种强制转换的存在允许编译器假定ba与4字节边界对齐(在一个平台上,需要对齐u32,许多编译器将在x86上这样做),之后它可以生成假定对齐的代码。
即使在x86平台上,也有一些指令非常失败:看似无辜的代码可以编译成机器代码,在运行时导致中止。UBSan应该在代码中捕捉到这一点,否则在运行它时会看起来很正常,并表现为“预期的”行为,但是如果使用另一组选项或不同的优化级别进行编译,则会失败。
编译器可以为memcpy生成正确的代码--而且经常会这样做,但这仅仅是因为编译器将知道未对齐的访问将在目标平台上工作并执行得足够好。
最后:
我不想在这里使用
memcpy,因为我不能依赖编译器对其进行足够好的优化。
这里的意思是:“我希望我的代码能够可靠地工作,只要是由垃圾编译或20年前生成缓慢代码的编译器编译的时候,就只能是。当然,当使用那些可以优化代码以使其快速运行的编译器编译时,绝对不是这样。”
发布于 2017-12-03 15:50:32
对象的原始类型最好是u32,一个u32数组.否则,您将通过使用memcpy来明智地处理这个问题。这不太可能成为现代系统的一个重要瓶颈;我不担心这一点。
在某些平台上,整数不可能存在于每个可能的地址。考虑您的系统的最大地址,我们可以假设在0xFFFFFFFFFFFFFFFF上。这里不可能存在一个四字节的整数,对吧?
有时,在硬件上执行优化以使总线(从CPU到各种外围设备、内存和其他什么的一系列连线)基于此对齐,其中之一是假设各种类型的地址仅以其大小的倍数出现。在这样一个平台上的错误访问很可能会导致陷阱(分段故障)。
因此,UBSan正确地警告您这个不可移植且难以调试的问题。
这个问题不仅会导致某些系统完全不能工作,而且您会发现允许您访问的系统需要在总线上进行第二次获取,以检索整数的第二部分。
在这段代码中还有其他一些问题。
printf("a = %d\n", a);如果希望打印int,则应使用%d。但是,你的论点是一个u32。不要像这样错配你的论点;这也是未定义的行为。我不确定u32是如何为您定义的,但我想最接近标准兼容的特性可能是uint32_t (来自<stdint.h>)。在任何要打印"%"PRIu32的地方,都应该使用uint32_t作为格式字符串。PRIu32 (来自<inttypes.h>)符号提供了实现定义的字符序列,这些字符序列将被实现printf函数识别。
请注意,此问题在其他地方重复出现,您正在使用的是u16类型:
printf("b = %d\n", b);"%"PRIu16可能就足够了。
https://stackoverflow.com/questions/47619944
复制相似问题