引用内存位置的内容.(x86 寻址模式) [英] Referencing the contents of a memory location. (x86 addressing modes)

查看:35
本文介绍了引用内存位置的内容.(x86 寻址模式)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个内存位置,其中包含一个我想与另一个字符进行比较的字符(它不在堆栈的顶部,所以我不能只是 pop 它).如何引用内存位置的内容以便进行比较?

基本上我如何在语法上做到这一点.

解决方案

有关寻址模式(16/32/64 位)的更多讨论,请参见 Agner Fog 的优化组装"指南,第 3.3 节.该指南比此答案提供了更多关于符号和/或 32 位位置无关代码等重定位的详细信息.

当然,英特尔和 AMD 的手册中有完整的章节介绍了 ModRM(以及可选的 SIB 和 disp8/disp32 字节)的编码细节,这清楚地说明了什么是可编码的以及存在限制的原因.

另见:不同寻址模式的 AT&T(GNU) 语法与 NASM 语法表,包括间接跳跃/呼叫.另请参阅此答案底部的链接集合.

<小时>

x86(32 位和 64 位)有多种寻址模式可供选择.它们都是以下形式:

[base_reg + index_reg*scale +位移] ;或者这个的一个子集[RIP + 位移] ;或 RIP 相关:仅限 64 位.不允许使用索引注册

(其中比例是 1、2、4 或 8,位移是有符号的 32 位常数).所有其他形式(除了 RIP 相关的)都是这个的子集,省略了一个或多个组件.这意味着你不需要一个归零的 index_reg 来访问 [rsi] 例如.

在 asm 源代码中,你写东西的顺序无关紧要:[5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2] 工作正常.(所有关于常数的数学运算都发生在组装时,导致一个常数位移.)

所有寄存器都必须具有相同的大小.并且与您所处的模式相同的大小,除非 您使用备用地址大小,需要额外的前缀字节.在 x32 ABI(长模式下的 ILP32) 之外,窄指针很少有用忽略寄存器的前 32 位,例如而不是使用 movsxd 将寄存器中 32 位可能为负的偏移量符号扩展为 64 位指针宽度.

如果你想使用al 作为数组索引,例如,您需要将其零或符号扩展到指针宽度.(有时可以在处理字节寄存器之前将 rax 的高位清零,这是实现此目的的好方法.)

<小时>

这些限制反映了机器代码中可编码的内容,这与汇编语言一样.比例因子是 2 位移位计数.ModRM(和可选的 SIB)字节最多可以编码 2 个寄存器,但不能更多,并且没有任何减法寄存器的模式,只能加法.任何寄存器都可以作为基.除了 ESP/RSP 之外的任何寄存器都可以是索引.请参阅rbp 不允许作为 SIB 基础? 了解编码详细信息,例如为什么 [rsp] 总是需要一个 SIB 字节.

一般情况的每个可能的子集都是可编码的,除了使用 e/rsp*scale 的那些(显然在正常"代码中没用,它总是在 esp).

通常,编码的代码大小为:

  • 1B 用于单寄存器模式(mod/rm(模式/寄存器或内存))
  • 2B 用于双寄存器模式(mod/rm + SIB(Scale Index Base)字节)
  • 位移可以是 0、1 或 4 个字节(符号扩展到 32 或 64,取决于地址大小).所以从 [-128 到 +127] 的位移可以使用更紧凑的 disp8 编码,比 disp32 节省 3 个字节.

ModRM 始终存在,它的位表示是否还存在 SIB.disp8/disp32 类似.代码大小异常:

  • [reg*scale] 本身只能用 32 位位移(当然可以为零)进行编码.智能汇编程序通过将 lea eax, [rdx*2] 编码为 lea eax, [rdx + rdx] 来解决这个问题,但该技巧仅适用于按比例缩放 2.无论哪种方式除了 ModRM 之外,还需要一个 SIB 字节.

  • 不可能将 e/rbpr13 编码为没有位移字节的基址寄存器,所以 [ebp] 是编码为 [ebp + byte 0].以ebp 作为基址寄存器的无位移编码意味着没有 基址寄存器(例如对于[disp + reg*scale]).

  • [e/rsp] 即使没有索引寄存器也需要一个 SIB 字节.(无论是否有位移).指定 [rsp] 的 mod/rm 编码意味着有一个 SIB 字节.

有关特殊情况的详细信息,请参阅英特尔参考手册中的表 2-5 及其周围部分.(它们在 32 位和 64 位模式下是相同的.添加 RIP 相关编码不会与任何其他编码发生冲突,即使没有 REX 前缀.)

为了性能,为了获得更小的 x86 机器代码而花费额外的指令通常是不值得的.在带有 uop 缓存的 Intel CPU 上,它比 L1 I$ 小,并且是更宝贵的资源.最小化融合域 uops 通常更为重要.

<小时>

如何使用

(这个问题被标记为 MASM,但是这个答案中的一些讨论了 NASM 版本的 Intel 语法,尤其是它们在 x86-64 RIP 相对寻址方面不同的地方.AT&T 语法没有被涵盖,但请记住,这只是相同机器码的另一种语法,因此限制是相同的.)

该表与可能的寻址模式的硬件编码并不完全匹配,因为我区分了使用标签(例如全局或静态数据)与使用小的常量位移.因此,我将介绍硬件寻址模式 + 链接器对符号的支持.

(注意:当源是一个字节时,通常你需要 movzx eax, byte [esi]movsx,但是 mov al, byte_src 确实会汇编并且在旧代码中很常见,合并到 EAX/RAX 的低字节.参见 为什么 GCC 不使用部分寄存器?如何在 64 位寄存器中隔离字节和字数组元素)

如果你有一个 int*,如果你有一个元素索引而不是一个字节偏移,你通常会使用比例因子按数组元素大小缩放索引.(出于代码大小的原因,优先使用字节偏移量或指针以避免索引寻址模式,以及在某些情况下的性能,尤其是在可能会损害微融合的 Intel CPU 上).但你也可以做其他事情.
如果esi中有一个指针char array*:

  • mov al, esi:无效,不会组装.没有方括号,它根本不是负载.这是一个错误,因为寄存器的大小不同.

  • mov al, [esi] 加载指向的字节,即array[0]*array.

  • mov al, [esi + ecx] 加载array[ecx].

  • mov al, [esi + 10] 加载array[10].

  • mov al, [esi + ecx*8 + 200] 加载array[ecx*8 + 200]

  • mov al, [global_array + 10]global_array[10] 加载.在 64 位模式下,这可以而且应该是 RIP 相对地址.推荐使用 NASM DEFAULT REL,默认情况下生成 RIP 相对地址,而不必总是使用 [rel global_array + 10].我认为 MASM 会默认执行此操作.无法直接使用具有 RIP 相对地址的索引寄存器.正常的方法是lea rax, [global_array] mov al, [rax + rcx*8 + 10] 或类似的.

    RIP 相关的变量引用(如[RIP + _a]")如何?在 x86-64 GAS Intel 语法工作中? 了解更多详细信息,以及 GAS .intel_syntax、NASM 和 GAS AT&T 语法的语法.

  • mov al, [global_array + ecx + edx*2 + 10]global_array[ecx + edx*2 + 10] 加载,显然你可以索引具有单个寄存器的静态/全局数组.甚至使用两个独立寄存器的二维阵列也是可能的.(对于除 2、4 或 8 以外的缩放因子,使用额外指令预缩放一个).请注意,global_array + 10 数学是在链接时完成的.目标文件(汇编器输出,链接器输入)通知链接器将 +10 添加到最终绝对地址,以将正确的 4 字节位移放入可执行文件(链接器输出)中.这就是为什么您不能在不是汇编时常量的链接时常量上使用任意表达式(例如符号地址).

    在64位模式下,这仍然需要global_array作为disp32部分的32位绝对地址,这才有效在 位置相关的 Linux 可执行文件中,或 largeaddressaware=no Windows.

  • mov al, 0ABh 根本不是负载,而是存储在指令中的立即数.(请注意,您需要在 0 前面加上前缀,以便汇编程序知道它是一个常量,而不是一个符号.一些汇编程序也接受 0xAB,而其中一些不接受0ABh:查看更多).

    可以使用符号作为立即数,将地址放入寄存器:

    • NASM:mov esi, global_array 组装成一个 mov esi, imm32,将地址放入 esi.
    • MASM:mov esi, OFFSET global_array 需要做同样的事情.
    • MASM:mov esi, global_array 组装成一个负载:mov esi, dword [global_array].

    在 64 位模式下,将符号地址放入寄存器的标准方法是 RIP 相关的 LEA.语法因汇编程序而异.MASM 默认情况下会这样做.NASM 需要一个default rel 指令,或[rel global_array].GAS 在每种寻址模式中都明确需要它.如何加载函数或标签的地址在 GNU 汇编器中注册.mov r64, imm64 通常也支持,用于 64 位绝对寻址,但通常是最慢的选项(代码大小会造成前端瓶颈).mov rdi, format_string/call printf 通常适用于 NASM,但效率不高.

    作为一种优化,当地址可以表示为 32 位 绝对(而不是从当前位置的 rel32 偏移量)时,mov reg, imm32 仍然是最佳就像在 32 位代码中一样.(Linux 非 PIE 可执行文件或带有 LargeAddressAware=no 的 Windows).但是请注意,在 32 位模式下,lea eax, [array] 效率不高:它浪费了一个字节的代码大小(ModRM + 绝对 disp32)并且可以't 在与 mov eax, imm32 一样多的执行端口上运行.32 位模式没有 RIP 相对寻址.

    请注意,OS X 在低 32 位之外的地址加载所有代码,因此 32 位绝对寻址不可用.可执行文件不需要与位置无关的代码,但您也可以这样做,因为 64 位绝对寻址的效率低于 RIP 相对寻址.macho64 目标文件格式不支持 32 位绝对地址的重定位 Linux ELF 的方式.确保不要在任何地方使用标签名称作为编译时 32 位常量.像 [global_array + constant] 这样的有效地址很好,因为它可以组合成 RIP 相对寻址模式.但是 [global_array + rcx] 是不允许的,因为 RIP 不能与任何其他寄存器一起使用,所以它必须与 global_array 的绝对地址进行硬组装 -编码为 32 位位移(将符号扩展为 64b).

<小时>

任何和所有这些寻址模式都可以LEA一起使用 以不影响标志的奖励进行整数数学运算,无论它是否是有效地址.对不是地址的值使用 LEA/指针?

[esi*4 + 10] 通常只对 LEA 有用(除非位移是一个符号,而不是一个小常数).在机器码中,没有单独对 scaled-register 进行编码,所以 [esi*4] 必须组装成 [esi*4 + 0],有 4 个字节的32 位位移为零.在一条指令中复制+移位而不是更短的 mov + shl 通常仍然值得,因为通常 uop 吞吐量比代码大小更成为瓶颈,尤其是在具有解码 uop 缓存的 CPU 上.

<小时>

您可以指定段覆盖,如 mov al, fs:[esi](NASM 语法).段覆盖只是在通常的编码前添加一个前缀字节.其他一切都保持不变,使用相同的语法.

您甚至可以使用具有 RIP 相对寻址的段覆盖.32 位绝对寻址比 RIP 相对寻址多一个字节来编码,因此 mov eax, fs:[0] 可以最有效地使用产生已知绝对地址的相对位移进行编码.即选择 rel32 所以 RIP+rel32 = 0.YASM 将使用 mov ecx, [fs: rel 0] 做到这一点,但 NASM 总是使用 disp32 绝对寻址,忽略 rel说明符.我还没有测试过 MASM 或 Gas.

<小时>

如果操作数大小不明确(例如在具有立即数和内存操作数的指令中),使用byte/word/dword/qword 来指定:

mov dword [rsi + 10], 123 ;国家安全管理协会mov dword ptr [rsi + 10], 123 ;MASM 和 GNU .intex_syntax noprefixmovl $123, 10(%rsi) # GNU(AT&T): 来自助记符后缀的操作数大小

请参阅 yasm 文档以获取 NASM 语法有效地址 和/或 维基百科 x86 条目关于寻址模式的部分.>

维基页面说明了 16 位模式下允许的内容.这是另一个 32 位寻址模式的备忘单".

<小时>

16 位寻址模式

16 位地址大小不能使用 SIB 字节,因此所有的一和二寄存器寻址模式都被编码到单个 mod/rm 字节中.reg1 可以是 BX 或 BP,reg2 可以是 SI 或 DI(或者你可以自己使用这 4 个寄存器中的任何一个).缩放不可用.16 位代码已经过时的原因有很多,包括这个,如果你不需要,不值得学习.

请注意,当使用地址大小前缀时,16 位限制适用于 32 位代码,因此 16 位 LEA 数学具有高度限制性.但是,您可以解决这个问题:lea eax, [edx + ecx*2] set ax = dx + cx*2, 因为源寄存器高位中的垃圾没有影响.

还有一个更详细的寻址模式指南,用于16位.16 位具有一组有限的寻址模式(只有少数寄存器是有效的,并且没有比例因子),但是您可能需要阅读它以了解有关 x86 CPU 如何使用地址的一些基础知识,因为其中一些没有改变32 位模式.

<小时>

相关主题:

上面有很多链接,但不是全部.

I have a memory location that contains a character that I want to compare with another character (and it's not at the top of the stack so I can't just pop it). How do I reference the contents of a memory location so I can compare it?

Basically how do I do it syntactically.

解决方案

For a more extended discussion of addressing modes (16/32/64bit), see Agner Fog's "Optimizing Assembly" guide, section 3.3. That guide has much more detail than this answer for relocation for symbols and or 32bit position-independent code, among other things.

And of course Intel and AMD's manuals have whole sections on the details of the encodings of ModRM (and optional SIB and disp8/disp32 bytes), which makes it clear what's encodeable and why limits exist.

See also: table of AT&T(GNU) syntax vs. NASM syntax for different addressing modes, including indirect jumps / calls. Also see the collection of links at the bottom of this answer.


x86 (32 and 64bit) has several addressing modes to choose from. They're all of the form:

[base_reg + index_reg*scale + displacement]      ; or a subset of this
[RIP + displacement]     ; or RIP-relative: 64bit only.  No index reg is allowed

(where scale is 1, 2, 4, or 8, and displacement is a signed 32-bit constant). All the other forms (except RIP-relative) are subsets of this that leave out one or more component. This means you don't need a zeroed index_reg to access [rsi] for example.

In asm source code, it doesn't matter what order you write things: [5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2] works fine. (All the math on constants happens at assemble time, resulting in a single constant displacement.)

The registers all have to be the same size as each other. And the same size as the mode you're in unless you use an alternate address-size, requiring an extra prefix byte. Narrow pointers are rarely useful outside of the x32 ABI (ILP32 in long mode) where you might want to ignore the top 32 bits of a register, e.g. instead of using movsxd to sign-extend a 32-bit possibly-negative offset in a register to 64-bit pointer width.

If you want to use al as an array index, for example, you need to zero- or sign-extend it to pointer width. (Having the upper bits of rax already zeroed before messing around with byte registers is sometimes possible, and is a good way to accomplish this.)


The limitations reflect what's encodeable in machine-code, as usual for assembly language. The scale factor is a 2-bit shift count. The ModRM (and optional SIB) bytes can encode up to 2 registers but not more, and don't have any modes that subtract registers, only add. Any register can be a base. Any register except ESP/RSP can be an index. See rbp not allowed as SIB base? for the encoding details, like why [rsp] always needs a SIB byte.

Every possible subset of the general case is encodable, except ones using e/rsp*scale (obviously useless in "normal" code that always keeps a pointer to stack memory in esp).

Normally, the code-size of the encodings is:

  • 1B for one-register modes (mod/rm (Mode / Register-or-memory))
  • 2B for two-register modes (mod/rm + SIB (Scale Index Base) byte)
  • displacement can be 0, 1, or 4 bytes (sign-extended to 32 or 64, depending on address-size). So displacements from [-128 to +127] can use the more compact disp8 encoding, saving 3 bytes vs. disp32.

ModRM is always present, and its bits signal whether a SIB is also present. Similar for disp8/disp32. Code-size exceptions:

  • [reg*scale] by itself can only be encoded with a 32-bit displacement (which can of course be zero). Smart assemblers work around that by encoding lea eax, [rdx*2] as lea eax, [rdx + rdx] but that trick only works for scaling by 2. Either way a SIB byte is required, in addition to ModRM.

  • It's impossible to encode e/rbp or r13 as the base register without a displacement byte, so [ebp] is encoded as [ebp + byte 0]. The no-displacement encodings with ebp as a base register instead mean there's no base register (e.g. for [disp + reg*scale]).

  • [e/rsp] requires a SIB byte even if there's no index register. (whether or not there's a displacement). The mod/rm encoding that would specify [rsp] instead means that there's a SIB byte.

See Table 2-5 in Intel's ref manual, and the surrounding section, for the details on the special cases. (They're the same in 32 and 64bit mode. Adding RIP-relative encoding didn't conflict with any other encoding, even without a REX prefix.)

For performance, it's typically not worth it to spend an extra instruction just to get smaller x86 machine code. On Intel CPUs with a uop cache, it's smaller than L1 I$, and a more precious resource. Minimizing fused-domain uops is typically more important.


How they're used

(This question was tagged MASM, but some of this answer talks about NASM's version of Intel syntax, especially where they differ for x86-64 RIP-relative addressing. AT&T syntax is not covered, but keep in mind that's just another syntax for the same machine code so the limitations are the same.)

This table doesn't exactly match the hardware encodings of possible addressing modes, since I'm distinguishing between using a label (for e.g. global or static data) vs. using a small constant displacement. So I'm covering hardware addressing modes + linker support for symbols.

(Note: usually you'd want movzx eax, byte [esi] or movsx when the source is a byte, but mov al, byte_src does assemble and is common in old code, merging into the low byte of EAX/RAX. See Why doesn't GCC use partial registers? and How to isolate byte and word array elements in a 64-bit register)

If you have an int*, often you'd use the scale factor to scale an index by the array element size if you have an element index instead of a byte offset. (Prefer byte offsets or pointers to avoid indexed addressing modes for code-size reasons, and performance in some cases especially on Intel CPUs where it can hurt micro-fusion). But you can also do other things.
If you have a pointer char array* in esi:

  • mov al, esi: invalid, won't assemble. Without square brackets, it's not a load at all. It's an error because the registers aren't the same size.

  • mov al, [esi] loads the byte pointed to, i.e. array[0] or *array.

  • mov al, [esi + ecx] loads array[ecx].

  • mov al, [esi + 10] loads array[10].

  • mov al, [esi + ecx*8 + 200] loads array[ecx*8 + 200]

  • mov al, [global_array + 10] loads from global_array[10]. In 64-bit mode, this can and should be a RIP-relative address. Using NASM DEFAULT REL is recommended, to generate RIP-relative addresses by default instead of having to always use [rel global_array + 10]. MASM does this by default I think. There is no way to use an index register with a RIP-relative address directly. The normal method is lea rax, [global_array] mov al, [rax + rcx*8 + 10] or similar.

    See How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? for more details, and syntax for GAS .intel_syntax, NASM, and GAS AT&T syntax.

  • mov al, [global_array + ecx + edx*2 + 10] loads from global_array[ecx + edx*2 + 10] Obviously you can index a static/global array with a single register. Even a 2D array using two separate registers is possible. (pre-scaling one with an extra instruction, for scale factors other than 2, 4, or 8). Note that the global_array + 10 math is done at link time. The object file (assembler output, linker input) informs the linker of the +10 to add to the final absolute address, to put the right 4-byte displacement into the executable (linker output). This is why you can't use arbitrary expressions on link-time constants that aren't assemble-time constants (e.g. symbol addresses).

    In 64-bit mode, this still needs the global_array as a 32-bit absolute address for the disp32 part, which only works in a position-dependent Linux executable, or largeaddressaware=no Windows.

  • mov al, 0ABh Not a load at all, but instead an immediate-constant that was stored inside the instruction. (Note that you need to prefix a 0 so the assembler knows it's a constant, not a symbol. Some assemblers will also accept 0xAB, and some of those won't accept 0ABh: see more).

    You can use a symbol as the immediate constant, to get an address into a register:

    • NASM: mov esi, global_array assembles into a mov esi, imm32 that puts the address into esi.
    • MASM: mov esi, OFFSET global_array is needed to do the same thing.
    • MASM: mov esi, global_array assembles into a load: mov esi, dword [global_array].

    In 64-bit mode, the standard way to put a symbol address into a register is a RIP-relative LEA. Syntax varies by assembler. MASM does it by default. NASM needs a default rel directive, or [rel global_array]. GAS needs it explicitly in every addressing mode. How to load address of function or label into register in GNU Assembler. mov r64, imm64 is usually supported too, for 64-bit absolute addressing, but is normally the slowest option (code size creates front-end bottlenecks). mov rdi, format_string / call printf typically works in NASM, but is not efficient.

    As an optimization when addresses can be represented as a 32-bit absolute (instead of as a rel32 offset from the current position), mov reg, imm32 is still optimal just like in 32-bit code. (Linux non-PIE executable or Windows with LargeAddressAware=no). But note that in 32-bit mode, lea eax, [array] is not efficient: it wastes a byte of code-size (ModRM + absolute disp32) and can't run on as many execution ports as mov eax, imm32. 32-bit mode doesn't have RIP-relative addressing.

    Note that OS X loads all code at an address outside the low 32 bits, so 32-bit absolute addressing is unusable. Position-independent code isn't required for executables, but you might as well because 64-bit absolute addressing is less efficient than RIP-relative. The macho64 object file format doesn't support relocations for 32-bit absolute addresses the way Linux ELF does. Make sure not to use a label name as a compile-time 32-bit constant anywhere. An effective-address like [global_array + constant] is fine because that can be assembled to a RIP-relative addressing mode. But [global_array + rcx] is not allowed because RIP can't be used with any other registers, so it would have to be assembled with the absolute address of global_array hard-coded as the 32bit displacement (which will be sign-extended to 64b).


Any and all of these addressing modes can be used with LEA to do integer math with a bonus of not affecting flags, regardless of whether it's a valid address. Using LEA on values that aren't addresses / pointers?

[esi*4 + 10] is usually only useful with LEA (unless the displacement is a symbol, instead of a small constant). In machine code, there is no encoding for scaled-register alone, so [esi*4] has to assemble to [esi*4 + 0], with 4 bytes of zeros for a 32-bit displacement. It's still often worth it to copy+shift in one instruction instead of a shorter mov + shl, because usually uop throughput is more of a bottleneck than code size, especially on CPUs with a decoded-uop cache.


You can specify segment-overrides like mov al, fs:[esi] (NASM syntax). A segment-override just adds a prefix-byte in front of the usual encoding. Everything else stays the same, with the same syntax.

You can even use segment overrides with RIP-relative addressing. 32-bit absolute addressing takes one more byte to encode than RIP-relative, so mov eax, fs:[0] can most efficiently be encoded using a relative displacement that produces a known absolute address. i.e. choose rel32 so RIP+rel32 = 0. YASM will do this with mov ecx, [fs: rel 0], but NASM always uses disp32 absolute addressing, ignoring the rel specifier. I haven't tested MASM or gas.


If the operand-size is ambiguous (e.g. in an instruction with an immediate and a memory operand), use byte / word / dword / qword to specify:

mov       dword [rsi + 10], 123   ; NASM
mov   dword ptr [rsi + 10], 123   ; MASM and GNU .intex_syntax noprefix

movl      $123, 10(%rsi)         # GNU(AT&T): operand size from mnemonic suffix

See the yasm docs for NASM-syntax effective addresses, and/or the wikipedia x86 entry's section on addressing modes.

The wiki page says what's allowed in 16bit mode. Here's another "cheat sheet" for 32bit addressing modes.


16-bit addressing modes

16bit address size can't use a SIB byte, so all the one and two register addressing modes are encoded into the single mod/rm byte. reg1 can be BX or BP, and reg2 can be SI or DI (or you can use any of those 4 registers by themself). Scaling is not available. 16bit code is obsolete for a lot of reasons, including this one, and not worth learning if you don't have to.

Note that the 16bit restrictions apply in 32bit code when the address-size prefix is used, so 16bit LEA-math is highly restrictive. However, you can work around that: lea eax, [edx + ecx*2] sets ax = dx + cx*2, because garbage in the upper bits of the source registers has no effect.

There's also a more detailed guide to addressing modes, for 16bit. 16-bit has a limited set of addressing modes (only a few registers are valid, and no scale factors), but you might want to read it to understand some fundamentals about how x86 CPUs use addresses because some of that hasn't changed for 32-bit mode.


Related topics:

Many of these are also linked above, but not all.

这篇关于引用内存位置的内容.(x86 寻址模式)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆