C/C++ 中结构体输出异常的原因(存储结构)

typedef struct{
    double d_num;
    char c;
    int num;
}Node;

int main(){
    Node n = {1.0000, 'a', 3};
    printf("%d %c %d\n", n.d_num, n.c, n.num);
}

其中应解释为64位double的部分被解释为32位整型。运行以上会显示97 0, 怎么用存储结构来解释这种异常?

阅读 4.4k
2 个回答

你用的是 32 位系统。我们 gcc -S 看下它的汇编:

 1      fld1
 2      fstpl   32(%esp)
 3      movb    $97, 40(%esp)
 4      movl    $3, 44(%esp)
 5      movl    44(%esp), %edx
 6      movzbl  40(%esp), %eax
 7      movsbl  %al, %eax
 8      fldl    32(%esp)
 9      movl    %edx, 16(%esp)
10      movl    %eax, 12(%esp)
11      fstpl   4(%esp)
12      movl    $.LC1, (%esp)
13      call    printf
14      movl    $0, %eax
  • 11 行,存入浮点数到地址 4(%esp),这是 printf 的第一个参数。通过 gdb 可以看到其十六进制表示为 0x00000000 0x3ff00000。把它的前一个字作为一个整数,即「0」;
  • 10 行,%eax 的值是把 40(%esp) 的低字节(第 6、7 行)弄过来的,即 'a',也就是打印出来的「97」;
  • printf%c 对应的那个 '\0' 是怎么来的呢?它是那个浮点数的后一个字的最低字节。

64 位系统上函数调用方式不一样。结果是 97, \3, 97。其中第三个「97」是编译器以为那个寄存器 printf 函数不会用所以残留的之前的值,因为它的第二个参数是浮点数,通过 %xmm0 寄存器传递的,但是 printf 认为没有浮点数所以不使用。不同参数所使用的寄存器没什么规律,如下:

The first 6 integer parameters in a function under Linux are passed in registers rdi , rsi, rdx, rex, r8 and r9

printf("%d %c %d\n", n.d_num, n.c, n.num);
首先观察应该更加细致一点,应该还有更多的东西你需要测试。
printf("%d\n%c\n%d\n", n.d_num, n.c, n.num);

下面进入正题: 首先我们看一下正常的情况,%lf %c %dprintf()函数是怎么运行的呢?它从右到做把%lf %c %d压到栈里面去,然后就去结构体中取数据了[2]。现在栈顶是 %lf, 那么取64位出来,就得到了d.d_num, 然后取8位的d.c,最后取32位的d.num。(我这里没有考虑内存对齐的问题和各种细节)

然后我们来看 %d %c %d,他取数据大小的顺序则是 32 8 32 ,ok,那么堆栈里的数据是

1.0000, 'a', 3

show_double(n.d_num);
show_char(n.c);
show_int(n.num);

// n.d_num: 0x000000000000f03f
// n.c    : 0x61
// n.num  : 0x03000000

我们看一下,32位全为0, 后面连续的8位也为0, man ascii '\0' => 0, 剩下的显示的n.num就是32位的0x61了,也就是a,至于最后的n.num,压根没动。

尝试把1.000改大一点,结果自然会变化。

参考:

  1. csapp show_bytes code
  2. stackoverflow: How does the particular C function work?
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题