"m" 约束不会隐式取消引用任何内容。 就像 "r" 约束一样,只是它扩展为保存表达式值的内存位置的寻址模式,而不是的一个寄存器。 (在 C 中,每个对象都有一个地址,尽管通常可以将其优化掉。)
作为 asm 的输入(或 "=m" 的输出)的 C 对象是您指定的左值或右值,例如"m"(var) 采用var 的值,而不是*var。所以你要添加指针。 (并告诉编译器您希望输入指针值在内存中,而不是寄存器中。)
也许你有一个指针但你称它为var,而不是ptr 或其他什么东西让你感到困惑? C 指针是一个对象,它的值是一个地址,它本身可以存储在内存中。如果您使用 C++,Word32 &var 将在您编写 var 时隐式取消引用。
在 C 语言中,你在写eax += ptr,但你想要eax += *ptr,所以你应该写
void test_func(Word32 *ptr){
asm( "add %[input], %%eax"
: // no inputs. Probably you should use "+a"(add_to_this) if you want the add result, and remove the EAX clobber.
: [input] "m"(*ptr) // the pointed-to Word32 in memory
: "eax" // the instruction modifies EAX; tell the compiler about it
);
}
编译 (Godbolt compiler explorer) 结果:
# gcc -O3 -m32
test_func:
movl 4(%esp), %edx # compiler-generated load of the function arg
add (%edx), %eax # from asm template, (%edx) filled in as %[input] for *ptr
ret
或者,如果您使用 -mregparm=3 或 64 位构建进行编译,则 arg 已经在寄存器中。例如64 位 GCC 发出 add (%rdi), %eax ; ret.
如果您在 C 中为返回 Word32 的函数编写了 return *ptr,并且没有内联 asm,则 asm 将类似,从堆栈中加载指针 arg,然后通过 mov (%edx), %eax 加载返回值。请参阅 Godbolt 链接。
如果内联 asm 没有达到您的预期,请查看编译器生成的 asm 以了解它是如何填充您的模板的。这有时可以帮助您弄清楚编译器认为您的意思。 (但前提是您了解基本设计原则。)
如果写"m"(ptr),编译如下:
void add_pointer(Word32 *ptr)
{
asm( "add %[input], %%eax" : : [input] "m"(ptr) : "eax" );
}
add_pointer:
add 4(%esp), %eax # ptr
ret
非常类似于如果你写了Word32 *bar(Word32 *ptr){ return ptr; }
请注意,如果您想增加内存位置,您可以使用"+m"(*ptr) 约束来告诉编译器指向的内存既是输入又是输出。或者,如果您只写到内存,"=m"(*ptr),这样它就可以优化到此内存位置的早期死存储。
另请参阅 How can I indicate that the memory *pointed* to by an inline ASM argument may be used? 以处理您使用 "r"(ptr) 输入并在 asm 中手动取消引用指针、访问您没有告诉编译器作为输入或输出操作数的内存的情况。
一般避免先做"r"(ptr),然后再手动做add (%0), %%eax。它需要额外的约束以使其安全,并且它强制编译器在寄存器中实现确切的地址,而不是使用寻址模式来相对于其他一些寄存器来访问它。例如4(%ecx) 如果在内联后发现您实际上是在将指针传递给数组或结构成员。
当然,通常要完全避免使用内联 asm,除非没有它就无法让编译器发出足够好的 asm。 https://gcc.gnu.org/wiki/DontUseInlineAsm。如果您决定使用它,请参阅https://stackoverflow.com/tags/inline-assembly/info 以获得避免常见错误的指南。