“(类型)变量”和“*((类型*)和变量)之间的区别是什么”,如果有的话?

时间:2013-11-18 18:31:00

标签: c pointers casting

我想知道:

之间是否存在差异
  1. 将基元变量转换为另一种基本类型
  2. 将原始变量的地址转换为另一个基本类型的指针
  3. 我还想知道是否有充分理由使用(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专家进行健全检查。

7 个答案:

答案 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

(demo)

<小时/> * 仅当其中一个类型是字符类型并且指针对齐有效,类型转换是微不足道的(即没有转换时),更改限定符或签名时,或者您使用struct / union转换为第一个成员是有效的转换源/目标。否则,这会导致未定义的行为。有关完整说明,请参阅C 2011(N1570),6.5 7。感谢Eric Postpischil,指出定义第二次转化时的情况。

答案 1 :(得分:4)

让我们看看两个简单的例子,在现代硬件上使用intfloat(没有有趣的业务)。

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)

不同之处在于,在第二种情况下,您可能有未定义的行为。原因是unsingedunsigned int相同而unsigned long可能比unsigned int大,当投射到您取消引用的指针时,您还会阅读未初始化的unsigned long部分。

第一种情况只是将unsigned int转换为unsigned long,并根据需要扩展unsigned int