首页 文章

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

提问于
浏览
2

请看下面的代码

#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;
}

我试图获取节点结构的 s 成员的地址, cast 它到不少于4个字节的数据类型( 4 字节是我机器上的 pointer 的宽度),然后取消引用指针作为参数 printf .

我认为两个printfs的结果应该是 same ,但第二个显示 "(null)" .

float和int在我的机器上具有相同的字节宽度,是导致这种情况的两种类型的 internal different representation 吗?

提前致谢 !

4 回答

  • 1

    您的程序调用未定义的行为,因为 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
    
  • 2

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

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

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

    在这种情况下,您需要理解的一个基本事情是,完全不可能将 float 值作为可变参数函数的可变参数传递 . 根据语言规范的要求,所有 float 值在传递之前会自动提升为 double 值 . (这同样适用于 charshort 值,它们始终首先提升为 int . )考虑到在您的情况下 float 值是通过重新解释指针对象占用的内存获得的,然后提升为 double ,因此,这并不奇怪你观察到的结果没有任何意义 .

    你需要理解的另一个基本的事情是C语言不允许重新解释由一种类型的对象和另一种类型的对象占用的内存(在某种意义上,结果行为是未定义的) . 不允许将指针对象占用的内存重新解释为 int 对象 . 这正是你想要做的 . 即使是你所说的第一个 printf ,也是偶然的 .

  • 6

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

  • 0

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

相关问题