C语言面试题详解(第2节)

相当多的程序员在求职时,都比较反感笔试,这其中有很大一部分原因是笔试题目常常比较“奇葩”,且几乎没有实践价值,更像是为了考倒求职者而出的。但是不得不承认,这些“奇葩”的问题也的确可以在一定程度上检验一个人的技术功底,对于我们,倒是一个检查自己知识盲点的好机会。

来看这道面试题

来看看这个中国某著名通信企业S公司的面试题:下面的C语言程序会输出什么?

#include <stdio.h>

int main()
{
     unsigned int a = 0xf7;
     unsigned char i = (unsigned char)a;
     char* b = (char*)&a;

     printf("%08x, %08x\n", i, *b);

     return 0;
}


这里仅仅只是做了一些数据类型转换而已,并没有其他额外额操作。那么,该如何分析呢?老规矩,得到答案最简单粗暴的方法就是写好C语言程序,编译执行一次,在我的机器上,上述程序输出结果如下:

# gcc t.c
# ./a.out 
000000f7, fffffff7


小伙伴们做对了吗?

“大端存储”和“小端存储”

首先应该明白的是,现代计算机存储数据的最小粒度是“字节”,也即计算机是逐字节存储数据的。C语言中的各种数据类型(如 char、int、double 等)之间最显著的区别就在于,它们存储数据时占用的内存空间不同。

现在来考虑变量 a 在内存中的存储。a 是 unsigned int 类型的,而C标准并没有定义 int 类型的数据占用多少字节内存,在我的机器上,int 类型占用 4 个字节,所以下面就以 int 占用 4 字节为例做分析了。

0xf7 仅占用一个字节的内存空间,而 a 占用了 4 个字节,a = 0xf7; 其实是将 0x000000f7 赋值给 a,那 a 在内存中是怎样存储 0x000000f7 的呢?请看下图:

有两种存储方式:一种是将数据的低位放在低地址单元,高位放在高地址单元,这就是所谓的数据“小端存储”方式。还有一种数据的“大端存储”方式则是反过来的,数据的高位被放在低地址单元,低位则被放在高地址单元。我的机器的数据存储方式属于“小端存储”,以下分析都是基于“小端存储”的。

进一步分析

知道了数据的两种存储方式后,再做进一步的分析就简单了。变量 i 是无符号 char 型的,它只占用一个字节的内存,i = (unsigned char)a; 的意思就是把 a 的最低一字节赋值给 i,赋值完成后 i 就等于 0xf7 了,经过 printf 的格式化输出,i 对应的显然会输出 000000f7。

接着分析:char* b 定义了一个有符号的 char 型指针变量 b, (char

)&a 可以分开看:&a 是取 a 的地址,加上(char

) 即再将其强制转换为 char* 类型指针。这么看来,char* b = (char*)&a; 赋值完成后,b 就指向 0xf7 这个数据了。

那为什么程序最后输出的不是 f7,而是 fffffff7 呢?

b 是有符号的 char 型指针变量,那么对于计算机来说, * b 从内存中取出的 0xf7 也是有符号的数据。对于有符号的数值,最高位是符号位,0xf7 的最高位为 1,显然它是一个负数。事实上,如果将 printf 中 * b 对应的 %08x 改为 %d,再编译 C语言程序并执行,结果如下:

# ./a.out 
000000f7, -9


容易看出,* b 其实等于 -9。但是,这也没能解释为什么最终程序会输出 fffffff7,而不是 000000f7 啊?这就是 printf 在“搞鬼”了。我们查看 printf 的C语言源代码,如下:

    296 int printf(const char *fmt, ...)
-   297 {
|   298     char printf_buf[1024];
|   299     va_list args;
|   300     int printed;
|   301 
|   302     va_start(args, fmt);
|   303     printed = vsprintf(printf_buf, fmt, args);
|   304     va_end(args);
|   305 
|   306     puts(printf_buf);
|   307 
|   308     return printed;
|   309 }


显然,核心是 vsprintf() 函数,继续跟踪, vsprintf() 函数的C语言源代码如下:

 int vsprintf(char *buf, const char *fmt, va_list args)
 {
     ...
         if (qualifier == 'l')
             num = va_arg(args, unsigned long);
         else if (qualifier == 'h') {
             num = (unsigned short)va_arg(args, int);
             if (flags & SIGN)
                 num = (short)num;
         } else if (flags & SIGN)
             num = va_arg(args, int);
         else
             num = va_arg(args, unsigned int);
         str = number(str, num, base, field_width, precision, flags);
     }
     *str = '\0';
     return str - buf;
}


可以看出,对于 %x,printf 实际上会将对应的整数转换为 unsigned int 类型。这也就是说,虽然 * b 从内存中只取出一个字节的数据,printf 还是会使用 4 个字节来表示它。这就明白了,用 4 个字节表示 -9,显然应该是 0xfffffff7。

至此,我们就完全弄懂了这个题目。当然,得到答案并不是目的,发现自己的知识盲点,巩固自己的基础,这才是初衷。

阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif