一道C语言访存题目的引申(From林健的BLOG)
来源:互联网 发布:怎样批量删除淘宝订单 编辑:程序博客网 时间:2024/05/17 22:48
毕业生求职的时节,非毕业生接触到各种面试、笔试题目的几率也会相应地增加。下面请看一道经典的 C 语言指针访存题目,稍有些经验的朋友应该很快可以看出这个题目考查的是字节序、内存布局等知识点。然后在大脑中略排列一下,就能够给出答案(2000000)。
- #include <stdio.h>
- int main()
- {
- int a[5] = {1, 2, 3, 4, 5};
- int *pa = (int)(&a) + 1;
- printf("%x/n", *pa);
- return 0;
- }
不过,这个答案是否绝对正确,还要看题目所处的上下文了。如果题目明确说是在常见的 32 位 x86平台上运行,那就无可厚非;但如果没有指明机器架构,那就要小心一点了,也许命题者真想考查一下求职者对非 x86平台的了解程度呢。如果考虑机器架构,这个题目应当如何作答呢?粗想一下,我们需要考虑的是字长、字节序和对齐(alignment)访问规则。不过真要做实验看看,会发现这里面还是有一些花样的。如果没有实际经验,只凭教条加推测,很可能想不到其它平台上的一些细节之处。
我们换用一段信息量更丰富的程序来进行后续的实验。在不同的平台上,均使用未加特殊参数的 gcc 来编译这段程序——
- #include <stdio.h>
- int main()
- {
- int x;
- int a[5] = {0x11121314, 0x21222324, 0x31323334, 0x41424344, 0x51525354};
- for (x = 0; x < 20; x++) {
- printf("%02x ", *(char *)((int)(&a) + x));
- }
- printf("/n");
- for (x = 0; x < 8; x++) {
- printf("%08x ", *(int *)((int)(&a) + x));
- }
- printf("/n");
- return 0;
- }
在 32 位 x86 下的结果不需要多解释。
- uname -a
- Linux ubuntu 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux
- ./a.out
- 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
- 11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421
而在 64 位的 x86_64 下,由于 8 字节的指针被截断到了 4 字节的整型长度,故会引发段错误。同样的情况出现在 64 位的Alpha 机器下。解决办法自然是把运算地址时的 int 修改成 long 或某种显式的 64 位类型。修改后的结果应该与 32 位 x86一致。
- uname -a
- Linux ubuntu 2.6.24-22-generic #1 SMP Mon Nov 24 19:35:06 UTC 2008 x86_64 GNU/Linux
- ./a.out
- Segmentation fault
- uname -a
- NetBSD sdf 2.1.0_STABLE NetBSD 2.1.0_STABLE (sdf) #0: Fri Mar 3002:24:32 UTC 2007 root@ol:/var/sys/arch/alpha/compile/sdf alpha
- ./a.out
- Memory fault (core dumped)
有趣的是在 XScale(Intel 实现的 ARMv5)下,虽然同属 little-endian,但非对齐取数时出现了在字内按字节循环的移位的结果。查查 ARM 的官方文档,这确实是 ARMv5 的特性;而在 ARMv6 以后,非对齐访问则是完全支持的。
- uname -a
- Linux zaurus 2.4.18-rmk7-pxa3-embedix #1 Sat, 06 Aug 2005 12:22:55 +0000 armv5tel unknown
- ./a.out
- 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
- 11121314 14111213 13141112 12131411 21222324 24212223 23242122 22232421
接下来看看 PowerPC,它是 big-endian 的代表,允许 32 位以内的非对齐访问,结果是容易理解的。有关 PowerPC 非对齐访问的一些细节可以参考这篇文章。
- uname -a
- AIX aix 3 5 00C97AC04C00 powerpc unknown AIX
- ./a.out
- 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
- 11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233
同样是 big-endium 的 SPARC 则不允许非对齐访问。它会对非对齐访问抛出 SIGBUS。
- uname -a
- SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris
- ./a.out
- 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
- Bus Error (core dumped)
最后看看我们中科院计算所的龙芯(Loongson)2E,它是兼容 MIPS 架构的处理器。很多教科书告诉我们说通常的 MIPS是不允许非对齐访问的(部分 MIPS 实现提供了非对齐访问指令,并申请了专利),但我们在龙芯下却得到了和 x86相同的、允许非对齐访问的结果,这又是为什么呢?初步查到的原因是“(针对龙芯修改过的 Linux)内核里确实有一个异常处理函数负责处理 lw 访问非对齐地址引起的异常”。这也许是龙芯绕开 MIPS 专利的一种办法?我会向龙芯团队的同学求证一下,也希望熟悉 MIPS 或龙芯的朋友给我一个确切的答案。
- uname -a
- Linux Loongson-1 2.6.18.1lemote #1 Sat Jan 13 16:02:26 CST 2007 mips GNU/Linux
- ./a.out
- 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
- 11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421
不过用心思考的朋友也许会发现上面一系列实验存在的一个疏漏:没有考虑编译器的影响。一方面,编译器可能对整型的字长有不同的规定(例如Windows 下的某些编译器即使在 32 位 x86 上也会把 int 定义为 16位);另一方面,编译器可以对不支持非对齐访问的处理器生成一定的指令序列、通过多次访存来模拟非对齐访问。我们看下面的例子:还是在 SPARC平台上,改用 Solaris 自带的 Sun CC 来编译实验程序,这时就不会出现“Bus Error”,而会输出和 PowerPC一样的结果。因为 SunCC 默认会使用“-xmemalign”参数来生成适当的访存指令序列。
- uname -a
- SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris
- cc data.c
- ./a.out
- 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
- 11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233
这样看来,在不指定机器架构和编译器等上下文的情况下,要正确且完美地回答一开始的那道题目还是需要一定知识积累的。答案省略,留给大家自己求解。在面试、笔试诸如 Sun SPARC、IBM PowerPC、中科院计算所微处理器中心等部门或者做 ARM等嵌入式开发的公司时,最好先了解清楚它们的产品常识。
- 一道C语言访存题目的引申(From林健的BLOG)
- 一道C语言的题目
- 一道C语言的内存管理题目
- 【C语言】一道给力的题目
- 一道C语言题目引发的讨论
- 一道简单C语言题目的优化
- 一道练习题引申出来的知识点(二) 正则表达式
- 一道C语言题目
- 分享C语言中的unsigned类型的一道题目
- 关于c语言的一道题目,适合新手看哦
- 每天一道C语言题目
- C语言(对内存的理解)
- C 语言的一道题~
- 请教一道C++的题目
- [C++]关于多态的一道题目
- 一道练习题引申出来的知识点(一) 文件以及文件流的操作
- 一道练习题引申出来的知识点(五) 常用的sql语句 以及相关概念
- 一道练习题引申出来的知识点(三) 集合/泛型集合
- 3.5 细粒度的对象模型
- sql和mysql
- 我的第一篇博客——宣言
- 一个等待Winexec执行完成后再继续执行后面代码的函数
- remove.exe
- 一道C语言访存题目的引申(From林健的BLOG)
- Java与C++的参数传递区别
- 从CEdit派生类,在激活输入是切换输入法为默认输入法
- Lotus Notes Send EMail from VB or VBA
- AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis (目录)
- 货币供应与股市和楼市
- Linux driver 介绍
- linux共享内存分配失败原因分析
- 设计模式读书笔记之代理模式(Proxy)