38

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

例如:

 align 4
 a: dw 0

它如何节省内存访问?

4

4 回答 4

49

我一直很喜欢 Samael 在以下线程中的全面解释:
ALIGN MASM 指令的解释,编译器如何解释该指令?

引用:

1. 使用方法

ALIGN X

ALIGN 指令带有一个数字 (X)。
这个数字 (X) 必须是 2 的幂。即 2、4、8、16 等等...

该指令允许您在指令之后立即在一个值 X 的倍数的内存地址上强制执行指令或数据的对齐。

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

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

2. 目的

A. 使用代码

如果指令在代码之前,原因将是优化(参考执行速度)。如果某些指令在 4 字节(32 位)边界上对齐,则它们的执行速度会更快。这种优化通常可以在时间要求严格的函数中使用或引用,例如为不断处理大量数据而设计的循环。但是,除了提高执行速度之外,没有“必要”将指令与代码一起使用。

B. 处理数据

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

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

下面是一篇关于数据对齐的有趣且信息量最大的文章,虽然是针对 MS C/C++ 编译器的,但内容如下:

IPF、x86 和 x64 上的 Windows 数据对齐,作者: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 字节边界的额外空间,以确保加载的内存对齐PE图像是正确的...

于 2012-06-30T21:27:10.717 回答
22

内存是固定宽度的,现在要么是 32 位,要么通常是 64 位宽(即使它是 32 位系统)。现在让我们假设一个 32 位数据总线。每次读取时,无论是 8 位、16 位还是 32 位,它都是 32 位总线,因此这些数据线上会有一些东西,只放置与对齐地址相关的 32 位是有意义的。

所以如果在地址 0x100 你有 32 位值 0x12345678。并且您要很好地执行 32 位读取,所有这些位都将在总线上。如果您要在地址 0x101 执行 8 位读取,内存控制器将读取地址 0x100,它将获得 0x12345678。从这 32 位中,它将隔离正确的“字节通道”,即与地址 0x101 相关的 8 位。一些处理器的内存控制器可能只看到 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 字节边界上。

“在 4 字节边界上对齐”仅表示地址模 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,你可以以那个速度提出请求,但很长一段时间没有回复)。基本上通过读取你有一个地址,但你必须等待数据,只要它需要。对于写入,您有两个项目,地址和数据,您可以“触发并忘记”您为内存控制器提供地址和数据,您的程序可以继续运行。如果下一条指令或一组指令需要访问内存、读取或写入,那么每个人都必须等待第一次写入完成,然后继续进行下一次访问。总线是 1333MHz/2,你可以以这个速度发出请求,但很长一段时间没有回复)。基本上通过读取你有一个地址,但你必须等待数据,只要它需要。对于写入,您有两个项目,地址和数据,您可以“触发并忘记”您为内存控制器提供地址和数据,您的程序可以继续运行。如果下一条指令或一组指令需要访问内存、读取或写入,那么每个人都必须等待第一次写入完成,然后继续进行下一次访问。总线是 1333MHz/2,你可以以这个速度发出请求,但很长一段时间没有回复)。基本上通过读取你有一个地址,但你必须等待数据,只要它需要。对于写入,您有两个项目,地址和数据,您可以“触发并忘记”您为内存控制器提供地址和数据,您的程序可以继续运行。如果下一条指令或一组指令需要访问内存、读取或写入,那么每个人都必须等待第一次写入完成,然后继续进行下一次访问。你给内存控制器地址和数据,你的程序可以继续运行。如果下一条指令或一组指令需要访问内存、读取或写入,那么每个人都必须等待第一次写入完成,然后继续进行下一次访问。你给内存控制器地址和数据,你的程序可以继续运行。如果下一条指令或一组指令需要访问内存、读取或写入,那么每个人都必须等待第一次写入完成,然后继续进行下一次访问。

以上所有内容都非常简单,但是您会在处理器和缓存之间看到,在缓存的另一侧,固定宽度的内存(缓存中 sram 的固定宽度和 dram 上的固定宽度)远侧不必匹配)缓存的另一侧在“缓存行”中访问,这些行通常是总线宽度大小的倍数。这对对齐既有帮助也有伤害。比如说 0x100 是一个高速缓存行边界。假设 0xFE 处的单词是一个缓存行的尾端,而 0x100 是下一个缓存行的开头。如果您要在地址 0xFE 执行 32 位读取,则不仅必须发生两个 32 位内存周期,而且还需要两次高速缓存行取指。最坏的情况是必须将两条缓存线移到内存中,以便为您正在获取的两条新缓存线腾出空间。

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

于 2012-07-01T05:45:03.517 回答
9

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

例如:

db 12h
align 4
db 32h

组装输出时:

0000  12 90 90 90 
0004  32

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

于 2012-06-30T21:25:49.423 回答
0

ALIGN 和 ALIGNB 宏提供了一种在单词、长字、段落或其他边界上对齐代码或数据的便捷方法(如 nasm 文档中所述,https: //nasm.us/doc/nasmdoc5.html )

于 2021-12-22T13:41:47.393 回答