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