【问题标题】:Why do I get "cast from pointer to integer of different size" error?为什么会出现“从指针转换为不同大小的整数”错误?
【发布时间】:2015-01-04 11:50:59
【问题描述】:

以下行(纯 c)在 windows(win7 64 位 + 代码块 13 + mingw32)和 debian(wheezy 32 位 + 代码块 10 + gcc)上干净地编译但会在 kali(64 位 + 代码块 + gcc)上发出警告。有cmets吗?我的意思是,为什么我会收到这个警告,尽管在 Windows 和 debian 上同一行编译时没有任何警告?

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    unsigned int offset = (unsigned int) dst % blkLen; // warning here!
    // some code cont...
}

代码块中的消息是:“error: cast from pointer to integer of different size [-Werror=pointer-to-int-cast]”

注意:我的编译器选项是-std=c99 -Werror -save-temps(在所有三个系统上都相同)。

编辑 2: 虽然我已经设法使用下面的预处理器行编译它而没有警告, @Keith Thompson(见下文)对此问题有一个关键点。所以,我最后的决定是使用uintptr_t 会是更好的选择。

编辑 1: 谢谢大家回复。正如所有回复所指出的,问题是 32 位与 64 位的问题。我插入了以下预处理器行:

#if __linux__   //  or #if __GNUC__
    #if __x86_64__ || __ppc64__
        #define ENVIRONMENT64
    #else
        #define ENVIRONMENT32
    #endif
#else
    #if _WIN32
        #define ENVIRONMENT32
    #else
        #define ENVIRONMENT64
    #endif
#endif // __linux__

#ifdef ENVIRONMENT64
    #define MAX_BLOCK_SIZE unsigned long long int
#else
    #define MAX_BLOCK_SIZE unsigned long int
#endif // ENVIRONMENT64

然后将问题行替换为:

unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;

现在,一切似乎都很好。

【问题讨论】:

  • 在某些系统/编译器上,int 的大小与指针的大小不同。但是,对于典型系统,偏移量的值将是 0...3。因此,将模的结果转换为 unsigned int 可以解决问题。

标签: c pointers gcc


【解决方案1】:

问题在于将void* 指针转换为unsigned int 本质上是不可移植的。

可能的大小差异只是问题的一部分。这部分问题可以通过使用uintptr_t(在<stdint.h><inttypes.h> 中定义的类型)来解决。 uintptr_t 保证足够宽,以便将 void* 转换为 uintptr_t 并再次返回将产生原始指针值(或至少一个与原始指针值比较相等的指针值)。还有一个类型intptr_t,是签名的;通常无符号类型对这种事情更有意义。 uintptr_tintptr_t 不保证存在,但它们应该存在于具有适当整数类型的任何(C99 或更高版本)实现中。

但是,即使您有一个足够大的整数类型来容纳转换后的指针,结果除了转换回指针之外,也不一定有意义。

C 标准在非规范脚注中说:

将指针转换为整数或整数的映射函数 指向指针的整数旨在与寻址一致 执行环境的结构。

除非您碰巧知道该寻址结构是什么,否则这是没有帮助的。

您似乎试图确定void* 参数的偏移量相对于blkLen 的下一个较低倍数;换句话说,您正在尝试确定指针值如何对齐相对于blkLen 大小的内存块。

如果您碰巧知道在您使用的系统上这样做是明智的,那很好。但是你应该知道,指针转换产生的整数算术运算本质上仍然是不可移植的。

一个具体的例子:我在系统(Cray 向量机)上工作过,其中void* 指针是一个 64 位机器地址(指向一个 64 位字),插入了一个 3 位字节偏移由软件进入其他未使用的高位 3位。将指针转换为整数只是复制了表示。除非考虑到这种(公认的异国情调)表示,否则对此类整数的任何整数运算都可能产生毫无意义的结果。

结论:

  1. 您绝对应该使用uintptr_t 而不是玩预处理器技巧来确定您可以使用哪种整数类型。编译器的实现者已经完成了确定可以安全地保存转换后的指针值的整数类型的工作。没有必要重新发明那个特定的轮子。 (警告:<stdint.h> 是由 1999 年 ISO 标准添加到 C 中的。如果你被一个没有实现它的古老编译器卡住了,你可能仍然需要使用某种#ifdef hacks。但我会仍然建议使用uintptr_t(如果可用)。您可以测试__STDC_VERSION__ >= 199901L 以测试C99 的一致性——尽管某些编译器可能支持<stdint.h> 而没有完全支持C99。)

  2. 您需要注意,将指针转换为整数并使用其值是不可移植的。这并不是说你不应该这样做。 C 的最大优势之一是它能够在您需要时支持不可移植代码。

【讨论】:

  • 这可能是关于该问题的最有价值的评论。非常感谢。我会考虑您的观点并重新编辑问题,以供所有关注者使用。
【解决方案2】:

您已经制作了宏,但您不认为它仍然是错误的。因为您的指针将被转换为 unsigned long long int 或 unsigned long int,这在 86x 和 64x 操作系统中将是 32 位和 64 位,但您的变量偏移量是 unsigned int,在 64x 和 86x 操作系统中是 32 位。 所以我认为你也应该将偏移量转换为相应的宏。

或者您也可以简单地将指针转换为 long(即 unsigned int 为 long)并将偏移量转换为 long(即 unsigned int 为 long)。

【讨论】:

    【解决方案3】:

    因为将void * 转换为unsigned int 正是该警告旨在捕获的内容,因为它不安全。指针可以是 64 位,int 可以是 32 位。对于任何给定的平台,sizeof(unsigned int) 不保证是sizeof(void *)。你应该改用uintptr_t

    【讨论】:

      【解决方案4】:

      我认为您会收到警告,因为 int 的大小取决于实现,例如 int 可能是 2 字节长或 4 字节长。这可能是警告的原因(如果我错了,请纠正我)。但无论如何,您为什么要对指针进行模运算。

      【讨论】:

      • 在许多但不是所有系统上。请参阅 Keith 的答案中的 Cray 示例,它不起作用。
      【解决方案5】:

      也许是因为在 64 位架构上,指针的长度为 64 位,而 int 的长度仅为 32 位?

      你应该试试

      void* foo(void *dst, ...) {
          // some code
          unsigned int blkLen = sizeof(int); // this line ok.
          uintptr_t offset = (uintptr_t) dst % blkLen; // warning here!
          // some code cont...
      }
      

      【讨论】:

        【解决方案6】:

        警告的原因是编译器怀疑您可能试图通过int 来回往返指针并返回。这是 64 位机器出现之前的常见做法,既不安全也不合理。当然在这里编译器可以清楚地看到你没有这样做,如果它足够聪明以避免在这种情况下发出警告,那就太好了,但事实并非如此。

        当转换值为负时,避免警告以及另一个更严重的错误结果问题的干净替代方案是:

        unsigned int offset = (uintptr_t) dst % blkLen;
        

        您需要包含stdint.hinttypes.h 才能使用uintptr_t

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多