int指针和浮点指针之间的奇怪区别

时间:2011-10-01 02:20:54

标签: c pointers floating-point int printf

请参阅下面的代码

#include <stdio.h>
#include <stddef.h>

typedef struct _node
{
int a;
char *s;
}Node, *nodePtr;

int main(int argc, char *argv[])
{
char *str = "string"; /*str points to satic storage area*/
Node nd;
nodePtr pNode = NULL;
size_t offset_of_s = offsetof(Node,s);

nd.a = 1;
nd.s = str;

pNode = &nd;

    /*Get addr of s, cast it to a different data types pointer, then de-reference it*/

/*this works, print "string"*/
printf("%s\n", *(int*)((char*)pNode + offset_of_s));

/*this sucks, print (null)*/
printf("%s\n", *(float*)((char*)pNode + offset_of_s));

return 0;
} 

我尝试将Node结构的 s 成员的地址强制获取到不少于4个字节的数据类型( 4 byte是我机器上指针的宽度),然后将指针取消引用作为 printf 的参数。

我认为两个printfs的结果应该是相同,但第二个显示“(null)”

float和int在我的机器上具有相同的字节宽度,导致这种情况的两种类型的内部不同表示是什么?

提前感谢!

4 个答案:

答案 0 :(得分:6)

您的程序调用未定义的行为,因为printf()的参数类型不是printf所期望的。通过查看源代码无法预测结果。

C99-TC3,§7.19.6.1/9

  

如果任何参数不是相应转换规范的正确类型,则行为未定义。

但是,如果您对您观察到的行为原因感兴趣,那么您的编译器可能是将浮点值传递给浮点CPU寄存器中的printf()的编译器之一。 (例如,GNU和CLang这样做)。对printf的第二次调用将取消引用的值放在浮点寄存器中,但printf看到%s转换说明符,查看了char*已经过的寄存器,可能一个通用寄存器,在你的情况下碰巧为零。

PS:这是GCC 4.6.1在我的linux上做出的

main:
    pushq   %rbx
    leal    .LC0(%rip), %ebx
    movl    $.LC1, %esi
    subq    $16, %rsp
    movl    %ebx, %edx
    movl    $1, %edi
    movq    $.LC0, 8(%rsp)
    xorl    %eax, %eax
    call    __printf_chk

    movd    %ebx, %xmm0
    movl    $.LC1, %esi
    movl    $1, %edi
    movl    $1, %eax
    unpcklps    %xmm0, %xmm0
    cvtps2pd    %xmm0, %xmm0 # this is where your value went
    call    __printf_chk     # is NOT gonna read from xmm0!

    addq    $16, %rsp
    xorl    %eax, %eax
    popq    %rbx
    ret

与clang 2.9相同的故事

    ...
    movl    $.L.str, %ebx
    xorb    %al, %al
    movl    $.L.str1, %edi     # .L.str1 is your format "%s\n"
    movl    $.L.str, %esi      # .L.str  is your static "string"
    callq   printf

    movd    %ebx, %xmm0        # your value is in xmm0 again
    cvtss2sd    %xmm0, %xmm0   # promoted to double, but still in xmm0
    movb    $1, %al
    movl    $.L.str1, %edi
    callq   printf             # printf has no idea

答案 1 :(得分:2)

您的期望显然基于您的信念,即可变函数的可变参数以某种特定方式传递给这些函数。这已经非常依赖于实现,所以从正式的C语言来看,你的实验已经没有多大意义了。

我猜你期望将可变参数复制到某种类型的“variadic argument array”(堆栈帧?)作为原始内存块,而不管它们的类型特定的语义。出于这个原因,您显然认为int参数应该以与float参数完全相同的方式传递,因为这两种类型在您的平台上碰巧具有相同的大小。

这个假设完全没有根据,也是不正确的。在这种情况下,实际传递给printf的是所讨论的参数的,并且因为这些值具有完全不同的特定于类型的语义,所以它们可以以完全不同的方式传递。毋庸置疑,代码的行为未定义的原因多于一个。

在这种情况下,您需要理解的一个基本问题是,将float值作为可变参数函数的可变参数传递是完全不可能的。根据语言规范的要求,所有float值在传递之前会自动提升为double值。 (这同样适用于charshort值,这些值始终首先提升为int。)考虑到在您的情况下,通过重新解释占用的内存来获取float值一个指针对象,然后提升为double,你所观察到的结果毫无意义就不足为奇了。

你需要理解的另一个基本的事情是C语言不允许重新解释由一种类型的对象和另一种类型的对象占用的内存(在某种意义上,结果行为是未定义的)。不允许将指针对象占用的内存重新解释为int对象。这正是你想要做的。即使是printf中的第一个,据称“按预期工作”,也只是偶然发生。

答案 2 :(得分:1)

是。二进制中a floatan integer的内部表示形式大不相同。

答案 3 :(得分:0)

如果需要地址,请在printf()中使用“%p”格式说明符。自K&amp; R2以来,它一直在C中,可能在之前。