64 位编译器中的 Memcpy 问题
Posted
技术标签:
【中文标题】64 位编译器中的 Memcpy 问题【英文标题】:Memcpy issue in 64 bit compiler 【发布时间】:2014-08-07 04:21:16 【问题描述】:对于初学者来说,我的主要目标是在 64 位环境中将 unsigned long 转换为 unsigned int(并且在没有任何编译警告的情况下执行此操作)。这可以通过这个函数轻松完成:
unsigned int a_to_b(unsigned long a)
return((unsigned int)(a));
虽然它给出了正确的结果,但问题是在 64 位环境中它会在编译时抛出警告:
1506-742 (I) 64 位可移植性:通过将 unsigned long int 类型转换为 unsigned int 类型可能会丢失数字。
为了绕过这个警告,我们尝试了这个函数:
unsigned int a_to_b(unsigned long a)
unsigned int b;
int skip = sizeof(unsigned long) - sizeof(unsigned int);
memcpy(&b, (&a)+skip, sizeof(b));
return(b);
现在这在编译时不会给出任何警告,但它不会给出正确的输出。 我在 32 位编译器中尝试了相同的代码,它工作正常。
请建议如何纠正 memcpy 的这种行为或任何其他绕过警告的方法。
【问题讨论】:
您是否考虑过long
可能比int
更长的可能性?
你用的是什么编译器? return a;
上的警告不会让我感到惊讶,但我会惊讶地看到像 return (unsigned int)a;
这样的明确案例的警告。一个演员对编译器说“相信我,我知道我在做什么。”
您知道您可以使用#pragma
抑制编译器警告吗? ***.com/questions/6440614/disable-warning-in-msvc2010
我很惊讶每个人都忽略了这里明显的 XY 问题。 :)
void main()
在 Unix 和 AIX 上是无条件错误的。您从int *
得到 0,因为 465287295 是 0x1BBBB87F(32 位值),并且在运行 AIX 的大端机器上,高位 4 字节(对于显示的值全为零)是存储在低位 4 字节之前。
【参考方案1】:
#include <limits.h>
unsigned int a_to_b(unsigned long a)
return(a & UINT_MAX);
在 Mac OS X 10.9.4 上,GCC 4.9.1 设置为非常挑剔:
gcc -O3 -g -std=c11 -Wall -Wextra -Werror -c cvt.c
我无法在 AIX(或者是 HP-UX?)上进行测试。
如评论中所述:
我真的不喜欢这个主意,但一种可能是:
union unsigned long ul; unsigned int ui[2]; u;
并将参数分配给
u.ul
并返回u.ui[0]
或u.ui[1]
(可能是后者,因为AIX 在大端的Power 芯片上运行,至少默认情况下如此)。这是一个骗子。编译器可能足以发现“误用”,但可能不会。
充实成半便携代码:
unsigned int a_to_b(unsigned long a)
const unsigned long byteorder = 0xFF;
const int LOW_ORDER_4_BYTES = (*(char *)&byteorder == 0);
union
unsigned long ul;
unsigned int ui[2];
u;
u.ul = a;
return(u.ui[LOW_ORDER_4_BYTES]);
在 little-endian 机器上测试,它给出了正确的结果。我认为它应该自动检测大端机器并在那里给出正确的结果,但我无法验证这一点。
完整的测试代码:
#include <stdio.h>
unsigned int a_to_b(unsigned long a);
unsigned int a_to_b(unsigned long a)
const unsigned long byteorder = 0xFF;
const int LOW_ORDER_4_BYTES = (*(char *)&byteorder == 0);
union
unsigned long ul;
unsigned int ui[2];
u;
u.ul = a;
return(u.ui[LOW_ORDER_4_BYTES]);
int main(void)
unsigned long x = 0xFEDCBA9876543210ULL;
printf("0x%.8X\n", a_to_b(x));
return 0;
输出:
0x76543210
【讨论】:
它是 AIX ,这可行,但它在编译期间给出了相同的警告。 不幸的是,我并不感到非常惊讶。那么,可能没有办法在 AIX 上抑制该警告,尽管您可以尝试查看编译器文档以查看是否还有其他可以做的事情。我真的不喜欢这个想法,但一种可能性是union unsigned long ul; unsigned int ui[2]; u;
并将参数分配给u.ul
并返回u.ui[0]
或u.ui[1]
(可能是后者,因为AIX 在大端的Power 芯片上运行,至少默认情况下)。这是一个骗子。编译器可能足以发现“误用”,但可能不会。【参考方案2】:
不要这样做,可能会导致特定机器中的陷阱表示。
你的电脑是小端的。你应该这样写:
unsigned int a_to_b(unsigned long a)
unsigned int b;
memcpy(&b, &a, sizeof(b));
return(b);
Big and Little Endian
【讨论】:
你真的不应该使用 memcpy 从一种类型转换为另一种类型。那真的是UB! 我在命中和试用期间也尝试过这个,但没有成功。 AIX 运行在大端机器上,而不是小端机器上(至少,默认情况下,运行 AIX 的 Power 芯片是大端机器)。 @IanNorton 使用memcpy
是实现类型双关语的最安全方法。这不是未定义的行为,更不用说“真正的 UB”。显然,它给出了实现定义的结果。
@PascalCuoq:理论上,如果架构具有陷阱表示(C99 §6.2.6.2),使用memcpy
可能会导致您将陷阱表示写入目标。但是,我不知道当前使用的任何现代架构都具有无符号整数类型(或任何整数类型)的陷阱表示。以上是关于64 位编译器中的 Memcpy 问题的主要内容,如果未能解决你的问题,请参考以下文章