这是什么意思来alignment堆栈?

我一直是一个高级编码人员,而且架构对我来说是相当新的,因此我决定在这里阅读关于Assembly的教程:

http://en.wikibooks.org/wiki/X86_Assembly/Print_Version

远在教程,指导如何转换Hello World! 程序

#include <stdio.h> int main(void) { printf("Hello, world!\n"); return 0; } 

转换为等效的汇编代码,并生成以下内容:

  .text LC0: .ascii "Hello, world!\12\0" .globl _main _main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp movl $0, %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax call __alloca call ___main movl $LC0, (%esp) call _printf movl $0, %eax leave ret 

对于其中一条线路,

 andl $-16, %esp 

解释是:

这个代码“和”ESP与0xFFFFFFF0,alignment堆栈与下一个最低的16字节的边界。 对Mingw的源代码的检查发现,这可能是SIMD指令出现在“_main”例程中,它只在alignment的地址上运行。 由于我们的例程不包含SIMD指令,因此这条线是不必要的。

我不明白这一点。 有人可以给我一个解释,意味着什么将堆栈与下一个16字节边界alignment,为什么它是必需的? 那么andl如何实现呢?

假设堆栈在_main入口处看起来像这样(堆栈指针的地址就是一个例子):

 | existing | | stack content | +-----------------+ <--- 0xbfff1230 

%ebp ,从%esp减去8,为局部variables保留一些空间:

 | existing | | stack content | +-----------------+ <--- 0xbfff1230 | %ebp | +-----------------+ <--- 0xbfff122c : reserved : : space : +-----------------+ <--- 0xbfff1224 

现在, andl指令andl %esp的低4位,这可能会降低它; 在这个特定的例子中,它具有保留另外4个字节的效果:

 | existing | | stack content | +-----------------+ <--- 0xbfff1230 | %ebp | +-----------------+ <--- 0xbfff122c : reserved : : space : + - - - - - - - - + <--- 0xbfff1224 : extra space : +-----------------+ <--- 0xbfff1220 

这个问题的关键是有一些“SIMD”(单指令,多数据)指令(在x86-land中也称为“SSE”,用于“SIMDstream扩展”),它可以对内存中的多个字执行并行操作,要求这些多个字是一个从16字节的倍数的地址开始的块。

一般来说,编译器不能假定来自%esp特定偏移将导致合适的地址(因为进入函数的%esp的状态取决于调用代码)。 但是,通过以这种方式有意地alignment堆栈指针,编译器知道将16个字节的任意倍数添加到堆栈指针将产生16字节的alignment地址,这对于这些SIMD指令是安全的。

这听起来不是特定于栈,而是一般的alignment。 也许想到整数倍这个词。

如果内存中的项目大小为1个字节,则表示它们全部alignment。 大小为两个字节的东西,那么整数次数2将被alignment,0,2,4,6,8等。非整数倍数1,3,5,7将不会alignment。 大小为4字节,整数倍数为0,4,8,12等的项目是alignment的,1,2,3,5,6,7等都不是。 8,0,8,16,24和16,16,32,48,64也是如此。

这意味着您可以查看该项目的基地址并确定它是否alignment。

大小以字节为单位,地址的forms为 
 1,xxxxxxx
 2,xxxxxx0
 4,xxxxx00
 8,xxxx000
 16,xxx0000
 32,xx00000
 64,x000000
等等

在编译器将数据与.text段中的指令混合的情况下,根据需要alignment数据相当简单(具体取决于体系结构)。 但是堆栈是一个运行时间的东西,编译器通常无法确定堆栈在运行时的位置。 所以在运行时,如果你有局部variables需要alignment,你需要让代码以编程方式调整栈。

例如,你有两个8字节的项目,总共16个字节,你真的希望他们alignment(在8字节边界)。 在进入时,函数会像往常一样从堆栈指针中减去16,为这两个项目腾出空间。 但是为了调整它们,需要更多的代码。 如果我们希望这两个8字节的项目在8个字节的边界上alignment,减去16后的堆栈指针是0xFF82,那么低3位不是0,所以它不alignment。 低三位是0b010。 在一般意义上,我们想从0xFF82中减去2来得到0xFF80。 我们如何确定它是一个2将与0b111(0x7)和减去这一数额。 这意味着一个和一个和一个减法操作。 但是如果我们和0x7(〜0x7 = 0xFFFF … FFF8)的值互补,我们可以采用一个快捷方式,使用一个alu操作得到0xFF80(只要编译器和处理器有一个单一的操作码方式,如果不是,它可能会比你更多和减去)。

这似乎是你的程序在做什么。 用-16和-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

所以要包装起来,如果你有一个典型的堆栈指针,从较高地址到较低地址的内存,

 
 sp = sp&(〜(n-1))

其中n是要alignment的字节数(必须是权力,但没关系,大多数alignment通常涉及两个权力)。 如果你已经说过做一个malloc(地址从低到高)并且想要alignment某个地址(记住malloc超过你所需要的至less是alignment的大小),那么

如果(ptr&(〜(n-)){ptr =(ptr + n)&(〜(n-1));}

或者,如果你想只是采取如果在那里,每次执行添加和掩码。

许多/大部分非x86体系结构都有alignment规则和要求。 x86就指令集而言过于灵活,但是就执行而言,您可以/不会在x86上对未alignment的访问支付罚款,所以即使您可以这样做,也应该努力保持alignment方式,就像您使用其他架构。 也许这就是这个代码在做什么。

这与字节alignment有关 。 某些体系结构要求将用于特定操作集的地址与特定的位边界alignment。

也就是说,例如,如果你想要一个指针的64位alignment,那么你可以在概念上将整个可寻址内存划分为从零开始的64位块。 如果一个地址恰好适合其中的一个块,那么这个地址就是“alignment的”,如果它把一个块和另一个块的一部分组合起来,就不会alignment。

字节alignment的一个重要特征(假设数是2的幂)是地址的最低有效X位总是零。 这允许处理器通过简单地不使用底部X位来以较less的位来表示更多的地址。

想象一下这个“绘图”

地址
  xxx0123456789abcdef01234567 ...
     [------] [------] [------] ...
寄存器

在地址倍数的值“8”容易地滑入(64位)寄存器

地址
          56789abc ...
     [------] [------] [------] ...
寄存器

当然,注册“步行”8个字节的步骤

现在,如果你想把地址xxx5的值写入寄存器要困难得多:-)


编辑和-16

-16是二进制的11111111111111111111111111110000

当你“和”任何与-16你得到一个价值的最后4位设置为0 …或多个16。

它应该只在偶数地址,而不是在奇数地址,因为存在性能不足的问题。

当处理器将内存中的数据加载到寄存器中时,需要通过基址和大小进行访问。 例如,它将从地址10100100获取4个字节。请注意,该示例末尾有两个零。 这是因为四个字节被存储,所以101001的前导位是重要的。 (处理器通过获取101001XX来通过“不关心”来访问这些)。

因此,alignment内存中的某些东西来重新排列数据(通常通过填充),以便所需项目的地址将具有足够的零字节。 继续上面的例子,我们不能从10100101中获取4个字节,因为最后两位不是0; 这会导致总线错误。 所以我们必须把地址提高到10101000(在这个过程中浪费了三个地址位置)。

编译器会自动为您执行此操作,并在汇编代码中进行表示。

请注意,这在C / C ++中被优化:

 struct first { char letter1; int number; char letter2; }; struct second { int number; char letter1; char letter2; }; int main () { cout << "Size of first: " << sizeof(first) << endl; cout << "Size of second: " << sizeof(second) << endl; return 0; } 

输出是

 Size of first: 12 Size of second: 8 

重新排列这两个char意味着int将被正确alignment,所以编译器不必通过填充来冲突基址。 这就是为什么第二个规模较小。