fmddlmyy的专栏

伐木丁丁鸟鸣嘤嘤

用户操作
[即时聊天] [发私信] [加为好友]
吕杰ID:fmddlmyy
83851次访问,排名1150好友9人,关注者11
伐木丁丁鸟鸣嘤嘤
fmddlmyy的文章
原创 41 篇
翻译 0 篇
转载 0 篇
评论 96 篇
fmddlmyy的公告

最近评论
Ben:为何我运行时,出现%%f was unexpected at this time.
ciml:很好的科普资料!
慧军:还是设计自己的UI方案比较好,不能认为WM流行就照抄WM的。WM更大的市场是在商业应用,UI上面就不见得好了。
abbot:very good!
htbegin:很不错!
文章分类
收藏
    相册
    个人主页
    fmddlmyy的留言板
    Mime 手机中文输入引擎
    伐木丁丁鸟鸣嘤嘤——我的个人主页
    历法计算程序
    我的程序
    我的随笔集
    存档
    订阅我的博客
    XML聚合  FeedSky

    原创 谈谈对齐(下)收藏

    新一篇: c8解绑机的0.2版 | 旧一篇: 谈谈对齐(中)

     

    谈谈对齐(下)

    3 数据对齐

    3.1 CISC和RISC

    CPU从指令集的特点上可以分为两类:CISC和RISC。CISC和RISC分别是复杂指令集计算机(Complex Instruction Set Computer)和精简指令集计算机(Reduced Instruction Set Computer)的缩写。

    CPU的工作可以看作以下步骤的反复循环:

    • step 1: 取指令
    • step 2: 取数据
    • step 3: 执行指令
    • step 4: 输出结果

    CISC CPU支持很多寻址模式,因此取数据的时间是不确定的。 RISC CPU的最大特点是简化了指令的寻址模式,除了Load/Store指令外,其它指令都采用寄存器寻址,即从寄存器读写数据。这种设计使取数据的时间相对稳定,可以简化指令流水线的设计。

    一般而言,RISC架构可以降低CPU的复杂性以及允许在同样的工艺水平下生产出功能更强大的CPU,但对于编译器的设计有更高的要求。

    3.2 对齐数据访问

    RISC CPU的Load/Store指令要求数据是对齐的。长度为4的数据应放在4n边界上,长度为2的数据应放在2n边界上。以ARM CPU的Load为例:

    LDR       R5,[R4]
    LDRSH     R7,[R6]
    LDRB      R9,[R8]
    

    LDR、LDRSH、LDRB分别从存储器读取一个字、半字和字节,放到指定寄存器。例如“LDR R5,[R4]”就是从R4指向的存储单元中读一个字(长度为4),放到R5中。 LDR要求数据地址在4n边界上,否则就会发生错误。LDRSH要求数据地址在2n边界上,否则就会发生错误。

    发生什么错误呢?这与具体的CPU有关,在ARM7TDMI上,非对齐访问会导致程序跳到数据访问错误的处理向量,即地址0x00000010处。在ARM920T上,LDR指令可能返回错误的数据。

    CISC的CPU支持非对齐的数据读取。

    3.3 例子

    我们来看一个例子:

    // 例子1
    void test(void)
    {
    	char a[] = {1,2,3,4,5};
    	int *pi, i;
    	
    	printf("&a[1]=%p\n", &a[1]);
    	pi = (int *)&a[1];
    	i = *pi;
    	printf("%08X\n", i);
    }
    

    关键是这句:

    	i = *pi;
    

    我们知道地址pi指向的4个字节依次是:0x02,0x03,0x04,0x05。在小尾的CPU上,我们期待的输出是05040302。让我们看看这段代码在不同平台的运行效果。

    3.3.1 PC/Windows

    输出结果是:

    &a[1]=0012FF25
    05040302
    

    符合我们的预期,也说明PC的CPU支持非对齐的数据读取。

    3.3.2 PC/Linux

    输出结果是:

    &a[1]=0xbfa0c36c
    05040302
    

    值得注意的是gcc编译器将局部变量a放在了1n边界(0xbfa0c36b)上。我们希望pi是一个奇数地址,将测试代码修改为:

    // 例子2
    void test(void)
    {
            int a[] = {0x04030201, 0x08070605};
            int *pi, i;
    
            pi = (int *)&((char *)&a)[1];
            printf("pi=%p\n", pi);
            i = *pi;
            printf("%08X\n", i);
    }
    

    输出结果是:

    pi=0xbfe87fe9
    05040302
    

    符合我们的预期。数据对齐是CPU的问题,和编译器、操作系统没有关系。

    3.3.3 ARM920T/Linux

    输出结果是:

    &a[1]=0xbec49e55
    01040302
    

    考虑到小尾,CPU实际读到的4个字节依次是0x02,0x03,0x04,0x01。这个结果不是我们所预期的,CPU出错了。

    3.3.4 ARM7TDMI

    程序在执行:

    	i = *pi;
    

    时直接跳回Data Abort的处理向量,即地址0x00000010。

    3.4 对策

    在读取紧缩结构或结构的紧缩成员时,编译器会自动产生按字节读取的代码。我们只要在做强制指针转换时细心一些就可以了。我们不应该将指向窄数据的指针强制转换成指向宽数据的指针。在可能发生数据对齐问题的地方,按字节读取数据。

    4 结束语

    我很欣赏一本叫作《玫瑰的名字》的小说。这是一本侦探小说,但给了我不少编程的启示。威廉教士在迷宫内解不开谜团,在迷宫外却推理出迷宫的真相。我也倾向于在头脑中调试程序,调试器只是不得已而用之。身陷其中,既会改变要测试的对象,也可能被表象迷惑。从外面观察,通过想象推理,有时更容易发现真相,或抓住调试的重点。本文讨论了一些与对齐相关的细节。多了解一些细节,有助于我们在头脑中形成更清晰的程序映像。

    发表于 @ 2008年07月24日 22:42:00|评论(loading...)|编辑|收藏

    新一篇: c8解绑机的0.2版 | 旧一篇: 谈谈对齐(中)

    评论:没有评论。

    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © fmddlmyy