下面的行(纯c)在windows(win7 64位+代码块13 + mingw32)和debian(wheezy 32位+代码块10 + gcc)上干净地编译,但在kali(64位+代码块+ gcc)上引发警告。任何意见?我的意思是,为什么我会收到这个警告,虽然同一行编译没有窗口和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...
}
codeblocks中的消息是:“error:从指针转换为不同大小的整数[-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
往返往返指针。这是64位机器出现之前的常见做法,并且不安全或不合理。当然,编译器在这里可以清楚地看到你没有这样做,如果它足够智能以避免在这种情况下发出警告,那就太好了,但事实并非如此。
避免警告的一个干净的替代方案,以及当转换后的值为负时另一个更糟糕的错误结果问题是:
unsigned int offset = (uintptr_t) dst % blkLen;
你需要包括stdint.h
或inttypes.h
才能获得uintptr_t
。
问题是将void*
指针转换为unsigned int
本质上是不可移植的。
尺寸的可能差异只是问题的一部分。问题的这一部分可以通过使用uintptr_t
和<stdint.h>
中定义的<inttypes.h>
来解决。 uintptr_t
保证足够宽,将void*
转换为uintptr_t
并再次返回将产生原始指针值(或至少比较原始指针值的指针值)。还有一种类型的intptr_t
,已签名;通常无符号类型对于这种事情更有意义。 uintptr_t
和intptr_t
不保证存在,但它们应该存在于具有适当整数类型的任何(C99或更高版本)实现上。
但即使你有一个大到足以保存转换指针的整数类型,结果对于转换回指针以外的任何其他内容都不一定有意义。
C标准在非规范性脚注中说:
用于将指针转换为整数或整数到指针的映射函数旨在与执行环境的寻址结构一致。
除非您碰巧知道该寻址结构是什么,否则这没有用。
你似乎试图确定void*
论证的偏移相对于blkLen
的下一个较低倍数;换句话说,您正在尝试确定指针值如何与blkLen
大小的内存块对齐。
如果你碰巧知道在你正在使用的系统上这是一个明智的做法,那很好。但是你应该知道,对指针转换产生的整数的算术运算本身仍然是不可移植的。
一个具体的例子:我曾经在系统(Cray矢量机器)上工作,其中void*
指针是64位机器地址(指向64位字),软件将3位字节偏移插入到其他位置。未使用的高阶3位。将指针转换为整数只是复制了表示。对这样的整数进行的任何整数运算都可能产生无意义的结果,除非它考虑到这种(无可否认的奇特)表示。
结论:
uintptr_t
而不是播放预处理器技巧来确定你可以使用哪种整数类型。编译器的实现者已经完成了确定可以安全地保存转换指针值的整数类型的工作。没有必要重新发明那个特定的轮子。 (警告:<stdint.h>
是按照1999 ISO标准添加到C中的。如果你使用一个没有实现它的古老编译器,你可能仍然需要使用某种#ifdef
黑客。但我仍然建议使用uintptr_t
如果它可用。您可以测试__STDC_VERSION__ >= 199901L
来测试C99一致性 - 尽管一些编译器可能支持<stdint.h>
而不完全支持C99。)因为将void *
投射到unsigned int
正是警告意图捕获的因为它是不安全的。指针可以是64位,int
可以是32位。对于任何给定的平台,sizeof(unsigned int)
不保证是sizeof(void *)
。你应该使用uintptr_t
代替。
也许是因为在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...
}
我认为你得到了警告,因为int的大小取决于实现,例如int可能是2字节长或4字节长。这可能是警告的原因(如果我错了请纠正我)。但无论如何你为什么试图在指针上做模数。
你已经制作了宏,但你不认为它仍然是错误的。因为你的指针将被转换为无符号long long int或unsigned long int,它在86x和64x OS中将是32位和64位,但你的变量offset是unsigned int,在64x和86x OS中是32位。所以我认为你应该将偏移转换为相应的宏。
或者只是你可以将指针转换为long(即unsigned int to long)和offset to long(即unsigned int to long)。