对齐一个部分的开头是什么意思?

时间:2012-06-30 21:03:43

标签: assembly

对齐一个部分的开头是什么意思?

例如:

 align 4
 a: dw 0

它如何节省内存访问?

3 个答案:

答案 0 :(得分:32)

我总是喜欢Samael在以下主题中的全面解释:
Explanation of the ALIGN MASM directive, How is this directive interpreted by the compiler?

引用:

1。 USAGE

ALIGN X

ALIGN指令附有一个数字(X) 这个数字(X)必须是2的幂。那就是2,4,8,16等......

该指令允许您在指令之后立即强制执行指令或数据的对齐,该存储器地址是值X的倍数。

在代码段的情况下,前面的指令/数据和ALIGN指令之后的额外空间用NULL指令(或等效的,如MOV EAX,EAX)填充,如果是,则为NULL数据段。

数字X不能大于引用ALIGN指令的段的默认对齐方式。它必须小于或等于段的默认对齐方式。更多关于这一点......

2。用途

:一种。使用代码

如果指令在代码之前,原因将是优化(参考执行速度)。如果它们在4字节(32位)边界上对齐,则某些指令执行得更快。 这种优化通常可以在时间关键函数中使用或引用,例如设计用于不断操作大量数据的循环。 除了执行速度的提高,没有必要的"但是,使用带有代码的指令。

<强> B中。使用数据

数据也是如此 - 我们主要使用该指令来提高执行速度 - 作为速度优化的一种手段。在某些情况下,数据错位会对我们的应用程序产生巨大的性能影响。

但是有了数据,有些情况下需要正确对齐,而不是奢侈品。在Itanium平台和SSE / SSE2指令集上尤其如此,其中128位边界(X = 16)上的未对齐可能会触发一般保护异常。

关于数据对齐的一篇有趣且内容最丰富的文章,虽然以MS C / C ++编译器为导向,但如下:

Windows Data Alignment on IPF, x86, and x64, by Kang Su Gatlin, MSDN

第3。段的默认对齐是什么?

A. 如果使用.386处理器指令,并且未明确声明段的默认对齐值,则默认段对齐的大小为DWORD(4字节)。 是的,在这种情况下,X = 4。 然后,您可以将以下值与ALIGN指令一起使用:(X = 2,X = 4)。 请记住,X必须小于或等于段对齐。

B. 如果使用.486处理器及其上述指令,并且未明确声明段的默认对齐值,则默认段对齐的大小为PARAGRAPH(16字节)。 在这种情况下,X = 16。 然后,您可以将以下值与ALIGN指令一起使用:(X = 2,X = 4,X = 8,X = 16)。

C。您可以通过以下方式声明具有非默认对齐的细分:

;Here, we create a code segment named "JUNK", which starts aligned on a 256 bytes boundary 
JUNK SEGMENT PAGE PUBLIC FLAT 'CODE'

;Your code starts aligned on a PAGE boundary (X=256)
; Possible values that can be used with the ALIGN directive 
; within this segment, are all the powers of 2, up to 256. 

JUNK ENDS

以下是段对齐值的别名...

Align Type     Starting Address 

BYTE             Next available byte address.
WORD          Next available word address (2 bytes per word).
DWORD        Next available double word address (4 bytes per double word).
PARA             Next available paragraph address (16 bytes per paragraph).
PAGE             Next available page address (256 bytes per page).

4。实施例

考虑以下示例(阅读有关ALIGN指令用法的注释)。

.486 
.MODEL FLAT,STDCALL 
OPTION CASEMAP:NONE 

INCLUDE \MASM32\INCLUDE\WINDOWS.INC 

.DATA

var1 BYTE  01; This variable is of 1 byte size. 
ALIGN 4

; We enforce the next variable to be alingned in the next memory 
;address that is multiple of 4. 
;This means that the extra space between the first  variable 
;and this one will be padded with nulls. ( 3 bytes in total)

var2 BYTE  02; This variable is of 1 byte size. 

ALIGN 2
; We enforce the next variable to be alingned in the next memory 
;address that is multiple of 2. 
;This means that the extra space between the second variable 
;and this  one will be padded with nulls. ( 1 byte in total)

var3 BYTE  03; This variable is of 1 byte size. 

.CODE
; Enforce the first instruction to be aligned on a memory address multiple of 4
ALIGN 4

EntryPoint:
; The following 3 instructions have 7 byte - opcodes 
; of the form 0F B6 05 XX XX XX XX
; In the following block, we do not enforce opcode
; alignment in memory...

MOVZX EAX, var1 
MOVZX EAX, var2 
MOVZX EAX, var3 

; The following 3 instructions have 7 byte - opcodes 
; of the form 0F B6 05 XX XX XX XX
; In the following block, we  enforce opcode alignment 
; for the third instruction, on a memory address multiple of 4.
; Since the second instruction opcodes end on a memory address 
; that is not a multiple of 4, some nops would be injected before 
; the first opcode  of the next instruction, so that the first opcode of it
; will start on a menory address that is a multiple of 4.


MOVZX EAX, var1 
MOVZX EAX, var2 
ALIGN 4 
MOVZX EAX, var3 

; The following 3 instructions have 7 byte - opcodes 
; of the form 0F B6 05 XX XX XX XX
; In the following block, we  enforce opcode alignment 
; for all instructions, on a memory address multiple of 4.
;The extra space between each instruction will be padded with NOPs

ALIGN 4
MOVZX EAX, var1
ALIGN 4
MOVZX EAX, var2
ALIGN 4
MOVZX EAX, var3


ALIGN 2
; The following  instruction has 1 byte - opcode (CC).
; In the following block, we  enforce opcode alignment 
; for the instruction, on a memory address multiple of 2.   
;The extra space between this instruction , 
;and the previous one,  will be padded with NOPs

INT 3
END EntryPoint

如果我们编译程序,那么这就是编译器生成的内容:

.DATA
;------------SNIP-SNIP------------------------------
.data:00402000 var1            db 1
.data:00402001                 db    0; This NULL was generated to enforce the alignment of the next instruction on an address that is a multiple of 4
.data:00402002                 db    0; This NULL was generated to enforce the alignment of the next instruction on an address that is a multiple of 4
.data:00402003                 db    0; This NULL was generated to enforce the alignment of the next instruction on an address that is a multiple of 4

.data:00402004 var2            db 2 
.data:00402005                 db    0; This NULL was generated to enforce the alignment of the next instruction oon an address that is a multiple of 2

.data:00402006 var3            db 3

.data:00402007                 db    0; The rest of the NULLs are to fill the memory page in which the segment will be loaded
;------------SNIP-SNIP------------------------------

.CODE
;------------SNIP-SNIP------------------------------

.text:00401000 start:
.text:00401000                 movzx   eax, var1
.text:00401007                 movzx   eax, var2
.text:0040100E                 movzx   eax, var3
.text:00401015                 movzx   eax, var1
.text:0040101C                 movzx   eax, var2
.text:00401023                 nop; This NOP was generated to enforce the alignment...
.text:00401024                 movzx   eax, var3
.text:0040102B                 nop; This NOP was generated to enforce the alignment...
.text:0040102C                 movzx   eax, var1
.text:00401033                 nop; This NOP was generated to enforce the alignment...
.text:00401034                 movzx   eax, var2
.text:0040103B                 nop; This NOP was generated to enforce the alignment...
.text:0040103C                 movzx   eax, var3
.text:00401043                 nop; This NOP was generated to enforce the alignment...
.text:00401044                 int     3              ; Trap to Debugger
.text:00401044; ---------------------------------------------------------------------------
.text:00401045                 db    0
.text:00401046                 db    0
.text:00401047                 db    0
.text:00401048                 db    0

;------------SNIP-SNIP------------------------------

如您所见,在应用程序的代码/数据结束后,编译器会生成更多指令/数据。这是因为PE部分在加载到内存中时,以PAGE大小(512字节)对齐。

因此,编译器使用垃圾字节(通常是INT 3指令,代码段的NOP或NULL,以及0FFh,数据段的NULL)将额外空间填充到下一个512字节的boudary中,以确保内存对齐加载的PE图像是正确的......

答案 1 :(得分:16)

存储器是固定宽度,现在是32位或通常是64位宽(即使它是32位系统)。让我们假设现在使用32位数据总线。每次执行读操作时,无论是8位,16位还是32位,它都是32位总线,因此这些数据线上会有一些内容,只需将32位与对齐的地址相关即可。

因此,如果在地址0x100处,您具有32位值0x12345678。你要执行32位读取,所有这些位都将在总线上。如果要在地址0x101执行8位读操作,则内​​存控制器将读取地址0x100,它将获得0x12345678。从这32位中,它将隔离正确的&#34;字节通道&#34;,8位与地址0x101相关。某些处理器内存控制器可能永远不会看到除32位读取之外的任何内容,处理器将处理隔离字节通道。

允许像x86这样的未对齐访问的处理器呢?如果地址为0x100时为0x12345678,地址为0x104时为0xAABBCCDD。并且要在这个基于32位数据总线的系统上对地址0x102进行32位读取,然后需要两个存储器周期,一个位于地址0x100,其中16位为所需值,然后另一个位于0x104,其中另外两个字节为找到。在这两次读取发生之后,您可以将32位拼凑在一起,并将其提供给请求处理器的更深处。如果你想在地址0x103处进行16位读取,会花费你两倍的内存周期,需要两倍的时间,就会发生同样的事情。

.align指令通常用汇编语言做什么(当然你必须指定精确的汇编器和处理器,因为这是一个指令,每个汇编器可以定义它想要为指令定义的任何东西)填充输出这样,紧跟在.align之后的东西就是在那个边界上对齐的。如果我有这个代码:

b: .db 0
c: .dw 0

事实证明,当我汇编并链接C的地址是0x102时,但我知道我将经常以32位值的形式访问它,然后我可以通过执行以下操作来对齐它:

b: .db 0
.align 4
c: .dw 0

假设在结果发生变化之前没有其他任何内容,那么b仍将在地址0x101,但汇编器将在b和c之间的二进制中再放两个字节,以便c更改为地址0x104,在4字节上对齐边界。

&#34;在4字节边界上对齐&#34;简单地表示模4的地址为零。基本上是0x0,0x4,0x8,0xc,0x10,0x14,0x18,0x1C等等。 (地址的低两位为零)。 8对齐表示地址的0x0,0x8,0x10,0x18或低3位为零。等等。

写入比读取更糟糕,因为您必须对小于总线的数据执行读取 - 修改 - 写入。如果我们想要更改地址0x101处的字节,我们将读取地址0x100处的32位值,更改一个字节,然后将该32位值写回0x100。因此,当您编写程序时,如果您认为通过使用较小的值来提高速度,则不是。因此,未对齐的写入和内存的宽度会使读取 - 修改 - 写入成本。未对齐写入的成本是读取的两倍。未对齐的写入将是两次读取 - 修改 - 写入。尽管如此,写入确实具有性能特征。当程序需要从内存中读取内容并立即使用该值时,下一条指令必须等待内存周期完成(这几天可能是数百个时钟周期,dram已经停留在133MHz大约十年,你的1333MHz DDR3内存不是1333MHz,总线是1333MHz / 2,你可以按照这个速度提出请求,但答案很长一段时间都没有回来)。基本上读取你有一个地址,但只要需要就必须等待数据。对于写作,你有两个项目,地址和数据,你可以“发射并忘记”#34;你给内存控制器的地址和数据,你的程序可以继续运行。如果下一条指令或一组指令需要访问内存,读取或写入,则每个人都必须等待第一次写入完成,然后继续进行下一次访问。

以上所有内容都非常简单,但您会在处理器和缓存之间,缓存的另一端,固定宽度内存(缓存中sram的固定宽度和固定宽度)中看到的内容在远端的dram不必匹配)在缓存的另一端访问&#34;缓存行&#34;它们通常是总线宽度大小的倍数。这既有利又有助于对齐。比如说0x100是一个缓存行边界。在0xFE的字是说是一个缓存行的尾端,而下一个开始的是0x100。如果要在地址0xFE处执行32位读取,则不仅必须发生两个32位存储器周期,而且还要进行两次高速缓存行读取。最糟糕的情况是必须将两个缓存行驱逐到内存中,以便为您提取的两个新缓存行腾出空间。如果你使用了一个对齐的地址,它仍然会很糟糕,但只有一半那么糟糕。

您的问题未指定处理器,但您的问题的性质意味着x86,这是众所周知的问题。其他处理器系列不允许未对齐访问,或者您必须专门禁用异常故障。有时,未对齐的访问不像x86那样。例如,如果您在地址0x100处有0x12345678,而在地址0x104处有0xAABBCCDD并且您已禁用故障并在地址0x102处执行了32位读取,则至少会有一个处理器,您将获得0x56781234。单个32位读取,字节通道旋转,将低位字节放在正确的位置。不,我不是在谈论x86系统,而是其他一些处理器。

答案 2 :(得分:4)

align用NOP / 0x90(NASM)填充地址,直到它与操作数对齐(addr模数操作数为零)。

例如:

db 12h
align 4
db 32h

汇编输出时:

0000  12 90 90 90 
0004  32

这对于内存访问来说更快,并且需要在x86 CPU(以及可能还有其他架构)中加载一些表。我无法列出任何具体案例,但您可以在SO和搜索引擎上找到several answers