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 问题的主要内容,如果未能解决你的问题,请参考以下文章

arm gcc5的交叉编译-正确使用memcpy

Matlab 编译器链接错误(64 位与 32 位)

从文件中读取 64 位整数字符串

使用 gcc 编译器时未在此范围内声明“memcpy”

为 iOS 项目编译 libtiff 以包含 64 位架构

OmniORB 编译错误 Windows 7 64 位