我想知道:
之间是否存在差异我还想知道是否有充分理由使用(2)超过(1)。我在遗留代码中看到过(2)这就是我想知道的原因。从上下文来看,我无法理解为什么(2)被优先于(1)。从我写的下面的测试中,我得出的结论是,至少上传的行为在两种情况下都是相同的:
/* compile with gcc -lm */
#include <stdio.h>
#include <math.h>
int main(void)
{
unsigned max_unsigned = pow(2, 8 * sizeof(unsigned)) - 1;
printf("VALUES:\n");
printf("%u\n", max_unsigned + 1);
printf("%lu\n", (unsigned long)max_unsigned + 1); /* case 1 */
printf("%lu\n", *((unsigned long *)&max_unsigned) + 1); /* case 2 */
printf("SIZES:\n");
printf("%d\n", sizeof(max_unsigned));
printf("%d\n", sizeof((unsigned long)max_unsigned)); /* case 1 */
printf("%d\n", sizeof(*((unsigned long *)&max_unsigned))); /* case 2 */
return 0;
}
输出:
VALUES:
0
4294967296
4294967296
SIZES:
4
8
8
从我的角度来看,(1)和(2)之间应该没有区别,但我想咨询SO专家进行健全检查。
答案 0 :(得分:9)
第一次演员是合法的;第二次演员可能不合法。
第一个演员告诉编译器使用变量类型的知识来转换为所需的类型;只要在语言标准中定义了适当的转换,编译器就会这样做。
第二个演员告诉编译器忘记它对变量类型的了解,并将其内部表示重新解释为不同类型 * 的内部表示。这具有有限的适用性:只要二进制表示与目标指针指向的类型匹配,该转换就可以工作。但是,这并不等同于第一次投射,因为在这种情况下,价值转换永远不会发生。
将正在转换的变量的类型切换为具有不同表示的变量(例如,float
),说明了这一点:第一次转换产生正确的结果,而第二次转换产生垃圾:
float test = 123456.0f;
printf("VALUES:\n");
printf("%f\n", test + 1);
printf("%lu\n", (unsigned long)test + 1);
printf("%lu\n", *((unsigned long *)&test) + 1); // Undefined behavior
打印
123457.000000
123457
1206984705
<小时/> * 仅当其中一个类型是字符类型并且指针对齐有效,类型转换是微不足道的(即没有转换时),更改限定符或签名时,或者您使用
struct
/ union
转换为第一个成员是有效的转换源/目标。否则,这会导致未定义的行为。有关完整说明,请参阅C 2011(N1570),6.5 7。感谢Eric Postpischil,指出定义第二次转化时的情况。
答案 1 :(得分:4)
让我们看看两个简单的例子,在现代硬件上使用int
和float
(没有有趣的业务)。
float x = 1.0f;
printf("(int) x = %d\n", (int) x);
printf("*(int *) &x = %d\n", *(int *) &x);
输出,也许......(您的结果可能会有所不同)
(int) x = 1 *(int *) &x = 1065353216
(int) x
会发生什么情况,您将值1.0f
转换为整数。
*(int *) &x
会发生什么,假装该值已经是整数。它不是一个整数。
1.0的浮点表示恰好是以下(二进制):
00111111 100000000 00000000 0000000
与整数1065353216的表示形式相同。
答案 2 :(得分:4)
此:
(type)variable
获取variable
的值,将转换为<{1}}。这种转换不一定只是复制表示的位;它遵循转换的语言规则。根据源和目标类型,结果可能与type
具有相同的数学值,但它可能完全不同。
此:
variable
执行名为别名的操作,有时非正式地称为type-punning。它占用了*((type *)&variable)
占用的内存块,并将其视为,就像它是variable
类型的对象一样。如果源类型和目标类型具有不同的表示形式(例如,整数和浮点类型),或者即使它们具有不同的大小,它也会产生奇怪的结果,甚至会导致程序崩溃。例如,如果type
是一个16位整数(比如,它的类型为variable
),并且short
是一个32位整数类型,那么最多你会得到一个32位结果包含16位垃圾 - 而简单的值转换会给你一个数学上正确的结果。
指针转换表单也可以为您提供对齐问题。例如,如果type
是字节对齐的,variable
需要2字节或4字节对齐,则可能会出现未定义的行为,这可能导致垃圾结果或程序崩溃。或者,更糟糕的是,它似乎可行(这意味着您有一个隐藏的错误,可能会在以后显示并且很难追踪)。
您可以通过获取对象的地址并将其转换为type
来检查对象的表示;该语言特别允许将任何对象视为字符类型数组。
但如果简单的价值转换完成了这项工作,那就是你应该使用的。
如果unsigned char*
和variable
都是算术运算,那么演员可能是不必要的;你可以将任何算术类型的表达式赋给任何算术类型的对象,转换将隐式完成。
这是两个表单具有非常不同行为的示例:
type
我系统上的输出(可能与您的不同)是:
#include <stdio.h>
int main(void) {
float x = 123.456;
printf("d = %g, sizeof (float) = %zu, sizeof (unsigned int) = %zu\n",
x, sizeof (float), sizeof (unsigned int));
printf("Value conversion: %u\n", (unsigned int)x);
printf("Aliasing : %u\n", *(unsigned int*)&x);
}
答案 3 :(得分:2)
“(type)variable”和“*((type *)&amp; variable)”之间有什么区别?如果有的话?
第二个表达式可能会导致对齐和锯齿问题。
第一种形式是将值转换为另一种类型的自然方式。但假设没有违反对齐或别名,在某些情况下,第二个表达式优于第一个表单。 *((type *)&variable)
将产生左值,而(type)variable
将不会产生左值(强制转换的结果绝不是左值)。
这允许您执行以下操作:
(*((type *)& expr)))++
请参阅Apple gcc
手册中的此选项,该手册执行类似的操作:
-fnon-lvalue-assign(仅限APPLE):只要遇到左值转换或左值条件表达式,编译器就会发出弃用警告 然后重写表达式如下:
(type)expr ---becomes---> *(type *)&expr cond ? expr1 : expr2 ---becomes---> *(cond ? &expr1 : &expr2)
答案 4 :(得分:1)
在处理结构时,投射指针会有所不同:
struct foo {
int a;
};
void foo()
{
int c;
((struct foo)(c)).a = 23; // bad
(*(struct foo *)(&c)).a = 42; // ok
}
答案 5 :(得分:0)
第一个((type)variable
是将变量简单地转换为所需类型,第二个(*(type*)&variable
)在被所需指针类型转换后解除指针。
答案 6 :(得分:0)
不同之处在于,在第二种情况下,您可能有未定义的行为。原因是unsinged
与unsigned int
相同而unsigned long
可能比unsigned int
大,当投射到您取消引用的指针时,您还会阅读未初始化的unsigned long
部分。
第一种情况只是将unsigned int
转换为unsigned long
,并根据需要扩展unsigned int
。