0

由于(根据我的阅读)ARM9 平台可能无法在未对齐的内存地址正确加载数据,让我们假设未对齐意味着地址值不是 2 的倍数(即在 16 位上未对齐),那么如何访问比如说,由正确对齐的指针指向的字符串上的第四个字符?

char buf[] = "Hello world.";

buf[3]; // (buf + 3) is unaligned here,  is it not?

编译器是否会生成额外的代码,而不是buf + 3正确对齐的情况?或者上面示例中的最后一条语句会在运行时产生不希望的结果 - 产生除第四个字符之外的其他内容,第二lHello

4

3 回答 3

4

字节访问不必对齐。编译器将生成一条ldrb指令,该指令不需要任何类型的对齐。

如果您好奇为什么,这是因为 ARM 将加载包含目标字节的整个对齐字,然后只需从刚加载的四个字节中选择该字节。

于 2013-06-29T17:43:08.560 回答
2

要记住的概念是编译器将尝试根据类型优化访问,以便获得处理器的最大效率。因此,在访问整数时,它会想要使用ldr指令之类的东西,如果它是未对齐的访问,则会出错。对于某些链接char访问,编译器将为您处理一些细节。你必须关心的是:

  • 铸造指针。如果将 achar *转换为 anint *并且指针未正确对齐,则会出现对齐陷阱。一般来说,向下(从 aint到 a char)是可以的,但反之则不行。你不想这样做:

    char buf[] = "12345678";
    int *p = &buf[1];
    printf("0x%08X\n", *p);  // *p is badness here!
    
  • 试图从结构中提取数据。我已经看到这样做了很多,这只是一个糟糕的做法。除了字节序问题,如果元素没有为平台正确对齐,您可能会导致对齐陷阱。

FWIW,铸造指针可能是我在实践中看到的第一个问题。

有一本很棒的书叫做Write Portable Code,它详细介绍了为多个平台编写代码的一些细节。链接站点上的示例章节实际上包含一个讨论对齐的部分。

还有更多的事情正在发生。内存函数,如malloc,还为您提供反向对齐的块(通常在双字边界上),以便您可以写入数据而不会遇到对齐错误。

最后一点,虽然较新的 ARM 可以更好地处理未对齐的访问,但这并不意味着它们是高性能的。这只是意味着他们是宽容的。X86 处理器也是如此。他们将执行未对齐的访问,但您这样做会强制获取额外的内存。

于 2013-06-30T11:38:00.427 回答
1

大多数系统使用基于字节的寻址。例如,地址 0x1234 以字节为单位。假设我的意思是这个答案的 8 位字节。

unaligned 的定义与传递的大小有关。例如,32 位传输是 4 个字节。4 是 2 的 2 次方,因此如果地址的低 2 位不是零,则该地址是未对齐的 32 位传输。

所以使用这样的表格或者只是理解 2 的幂

8   1  0 []
16  2  1 [0]
32  4  2 [1:0]
64  8  3 [2:0]
128 16 4 [3:0]

第一列是传输中的位数。第二个是表示的字节数,第三个是地址底部必须为零才能使其成为对齐传输的位数,最后一列描述了这些位。

不可能进行未对齐的 8 位传输。不在手臂上,不在任何系统上。请理解。

16 位传输。一旦我们开始传输大于 16 位的数据,您就可以开始谈论未对齐的问题。那么未对齐传输的问题与总线周期数有关。假设您在具有 16 位宽总线和 16 位宽存储器的系统上进行 16 位传输。这意味着我们在这些地址的内存中有项目,例如,左边的地址,右边的数据:

0x0100 : 0x1234
0x0102 : 0x5678

如果要进行对齐的 16 位传输,则地址的 lsbit 必须为零,0x100、0x102、0x104 等。未对齐的传输将位于设置 lsbit 的地址,0x101、0x103、0x105 等。为什么是他们有问题吗?在这个假设的(曾经存在并且仍然存在这样的真实系统)系统中,为了在地址 0x0100 处获取两个字节,我们只需要访问一次内存并从该地址获取所有 16 位,从而得到 0x1234。但是如果我们想要从地址 0x0101 开始的 16 位。我们必须执行两个内存事务 0x0100 和 0x0102 并从每个事务中获取一个字节,将它们组合起来以获得小端为 0x7812 的结果。这需要更多的时钟周期、更多的逻辑等。效率低下且成本高昂。那个时代的英特尔 x86 和其他系统是 8 位或 16 位处理器但使用 8 位内存,

较旧的arm可能来自那个时代,也可能不是那个时代,但是在橡子之后,从通用寄存器大小的角度来看,armv4到现在都是32位系统,数据总线是32位或64位(最新的arm 有 64 位寄存器,如果不是 128 位总线,我会假设)取决于您的系统。将 ARM 放在地图上的核心是 ARM7TDMI,它是一个 ARMv4T,我假设是一个 32 位数据总线。ARM7 和 ARM9 ARM ARM(ARM 架构参考手册)在每个修订版中都更改了其语言(我有几个修订版回到纸质版),涉及到诸如 UNPREDICTABLE RESULTS 之类的词。他们何时何地会将某些东西列为坏的或损坏的。其中一些是合法的,了解 ARM 不生产芯片,他们出售 IP,那时它是特定铸造厂的面具,今天你将源代码带到他们的核心并处理它。因此,为了生存,您需要一个良好的法律辩护,您的秘密会暴露给客户,如果 ARM 要找到这些项目的克隆(这是另一个法律讨论),其中一些声称不受支持的项目实际上具有确定性结果不可预测的结果是可预测的,并且与什么武器逻辑相匹配,你必须非常善于解释原因。克隆人在尝试过(或合法成为许可的手臂核心)时已被粉碎,因此其中一些只是有趣的历史。另一本 arm 手册非常清楚地描述了在旧 ARM7 系统上进行未对齐传输时会发生什么。当你看到它时,它是一个有点糟糕的时刻,

字节通道旋转。在系统某处的 32 位总线上,可能不在 amba/axi 总线上,而是在内存控制器内部,您将有效地得到以下信息:

0x0100 : 0x12345678
0x0101 : 0x78123456
0x0102 : 0x56781234
0x0103 : 0x34567812

地址在左边,结果数据在右边。现在你问为什么这么明显?转移的规模是多少?传输的大小无关紧要,不要紧,这样看那个地址/数据:

0x0100 : 0x12345678
0x0101 : 0xxx123456
0x0102 : 0xxxxx1234
0x0103 : 0xxxxxxx12

使用对齐传输,0x0100 对于 32、16 和 8 位是合法的,查看低 8、16 或 32 位,您会得到正确答案,如图所示。对于地址 0x0101,只有 8 位传输是合法的,并且该数据的低 8 位在低 8 位中,只需将它们复制到寄存器的低 8 位即可。对于地址 0x0102,8 和 16 是合法的,未对齐的,传输,0x1234 是 16 位的正确答案,0x34 是 8 的正确答案。最后,0x0103 8 位是唯一没有对齐问题的传输大小,0x12 是正确答案。

以上信息均来自公开文档,这里没有秘密或特殊内幕知识,只是通用的编程经验。

ARM 在数据中止或预取中止(thumb 是一个单独的主题)中设置了一个例外,以阻止像其他架构一样使用未对齐的传输。不幸的是,x86 导致人们变得非常懒惰,并且也不关心他们在 x86 上执行此类操作时所招致的性能损失,这允许以额外的周期和额外的逻辑为代价进行传输。如果我记得的话,预取中止在我使用的 ARM7 平台上默认情况下是不开启的,但在我使用的 ARM9 平台上默认情况下是开启的,我的记忆可能是错误的,因为我不知道默认设置是如何工作的内核上的选项,因此它可能因芯片和供应商而异。只要您了解数据发生的情况,您就可以禁用它并进行未对齐的传输(旋转而不是溢出到下一个单词)。

更现代的 ARM 处理器确实支持非对齐传输,正如人们所期望的那样,我不会在这里使用 64 位示例来节省打字和空间,而是回到那个 16 位示例来绘制图片

0x0100: 0x1234
0x0102: 0x5678

对于 16 位宽的系统、内存和总线、小端序,如果您在地址 0x0101 进行 16 位未对齐传输,您会期望看到 0x7812,这就是您现在在现代 arm 系统上得到的。但它仍然是一个软件控制的功能,您可以启用未对齐传输的异常,您将获得数据中止而不是完成传输。

至于您的问题,请查看 ldrb 指令,该指令从内存中读取 8 位,是 8 位,没有未对齐的所有地址都是有效的,如果 buf[] 恰好位于地址 0x1234 然后 buf[ 3] 位于地址 0x1237 处,对于 8 位读取而言,这是一个完全有效的地址。没有任何类型的对齐问题,不会触发任何异常。如果您执行以下非常丑陋的编程技巧之一,您会遇到麻烦:

char buf[]="hello world";
short *sptr;
int *iptr;

    sptr=(short *)&buf[3];
    iptr=(int *)&buf[3];
    ...
    something=*sptr;
    something=*iptr;
    ...
    short_something=*(short *)&buf[3];
    int_something=*(int *)&buf[3];

然后是的,您需要担心未对齐的传输,并希望您没有任何编译器优化问题,从而使代码无法像您想象的那样工作。+1 到 jszakmeister 已经涵盖了这个子主题。

简短的回答:

char buf[]="hello world";

通常假定 char 表示 8 位字节,因此这是 8 位项目的数量。当然是为 ARM 编译的,这就是你将得到的(或 mips 或 x86 或 power pc 等)。因此,访问该字符串中任何 X 的 buf[X] 不能不对齐,因为

something = buf[X];

是 8 位传输,您不能进行未对齐的 8 位传输。如果你要这样做

short buf[]={1,2,1,2,3,2,1};

假设 short 是 16 位,但并非总是如此,对于 arm 编译器,我知道它是 16 位。但是这里的 buf[X] 也不能不对齐,因为编译器会为您计算偏移量。如下 buf[X] 的地址为 base_address_of_buf + (X<<1)。并且编译器和/或链接器将确保在 ARM、MIPS 和其他系统上将 buf 放置在 16 位对齐的地址上,以便数学始终会产生对齐的地址。

于 2013-06-30T15:30:20.670 回答