Silver

自我的人有个优点,不瞎编排别人。
"The nice thingabout egotists is that they don't talk about other people."
-Lucille S. Harper

一道“简单”的二进制题目

Last updated:May.03, 2018 CST 19:03:10

这道题本来想当作面试题给出,但认真考虑了一下,还是算了,怕被找上门真人快打……找好的懂底层的开发真难啊。

题目

请指出下列代码是否存在问题。如有,请指明错误,并预测程序运行输出,并说明原因。

// gcc test.c -o test -g && ./test
#include <stdio.h>

int main(void){
    unsigned int i = 0x12345678;
    float j = 0x9abcdef0;
    printf("%d\t%f\t", j, i);
    printf("%d\t%f\n", i, j);

    printf("%p\t%x\t", j, i);
    printf("%p\t%x\n", i, j);
}

答案

这道题的来源是最近同事调试exp中遇到的小问题,主要涉及到了:

首先说下正确的答案:

存在错误,在第三次和第四次时传入参数类型和格式化字符串不符。打印输出的第一行可能是:
305419896   2596069120.000000   305419896   2596069120.000000
第二行中第一个、第三个输出数字为0x12345678,第二个和第四个不可预测。

分析

一般来说比较容易从源代码发现的问题是:

但观察输出,又有了几个新问题,分别是:

printf的特殊之处在于,他是一个变参函数。变参函数的传参方式,在System V AMD64 ABI(Page 20)中有如下描述:

而在printf->vfprintf内部的代码中,处理这些变参的方法如下:

    LABEL (form_integer):                                                      \
      /* Signed decimal integer.  */                                              \
      base = 10;                                                              \
                                                                              \
      if (is_longlong)                                                              \
        {                                                                      \
          long long int signed_number;                                              \
                                                                              \
          if (fspec == NULL)                                                      \
            signed_number = va_arg (ap, long long int);                              \
          else                                                                      \
            signed_number = args_value[fspec->data_arg].pa_long_long_int;     \

//...
    LABEL (form_float):                                                              \
      {                                                                              \
        /* Floating-point number.  This is handled by printf_fp.c.  */              \
  //...
            if (is_long_double)                                                      \
              the_arg.pa_long_double = va_arg (ap, long double);              \
            else                                                              \
              the_arg.pa_double = va_arg (ap, double);                              \
            ptr = (const void *) &the_arg;                                      \
                                                                              \

va_arg总是按照这个宏里指定的类型提取参数。


说到这,上面两个问题的答案都明确了:

尝试更改编译参数加入-Wall,会发现出现了编译警告。此外,类似的问题在ARM64等其他平台下仍然存在,可以自行检索查询。

Contact webmaster at:
[email protected]