在c++中使用asm代码操作寄存器时会发生什么?
What happens to registers when you manipulate them using asm code in C++?
部分代码:
int x = 1;
for(int i = 1; i < 10; i++)
{
x *= i;
__asm {
mov eax, x
};
}
如果这个程序使用eax
来增加i
的值,当我操作eax
时会发生什么?
编译器是否会保存__asm
调用之前的寄存器,并在asm代码执行后使用它们,还是会忽略eax
被操纵并继续产生某种奇怪的行为?
exx内部发生了什么?
EDIT:即使我的代码只适用于Visual c++,我想知道一般情况下会发生什么,以及不同的编译器将如何处理这一点。
exx内部发生了什么?
内联asm既不神奇也不特殊。c++编译器已经将c++翻译成asm。
您的内联asm只是包含在编译器生成的asm中。如果你想了解真正发生了什么,看看编译器的asm输出,看看你的代码是如何适应的。
。这部分问题的答案是,它取决于编译器、上下文和优化选项,所以你应该自己看看生成的asm。
你的问题使用msvc风格的内联asm,它保存/恢复内联asm周围的寄存器(除了ebp
,当然还有esp
)。所以我认为你的例子永远不会有效果。msvc风格没有语法来向编译器传达关于寄存器使用的任何信息,或者在寄存器中获取值而不是在内存中获取值。
您有时会看到MSVC内联asm在int
函数结束时在eax
中留下一个值,而没有return
语句,在有限的情况下,编译器不会对内联asm结束和函数结束之间的eax做任何事情。
你在评论中说你想要一个g++的答案,它甚至不能编译你的例子,但无论如何,我将为你写一个。
GNU C内联asm使用不同的语法,这需要您告诉编译器哪些寄存器是输入(不修改),哪些是读写或仅写。
这取决于程序员使用约束向编译器正确描述asm,否则你会踩到它的脚趾。使用GNU C内联asm就像跳舞一样,你可以潜在地获得好的结果,但是如果你不小心,你会踩到编译器的脚趾。(此外,通常编译器可以自己生成很好的asm,而内联asm是一种非常脆弱的优化方式;其中一个主要问题是,内联之后的常量传播不可能通过内联asm实现。
无论如何,让我们用一个GNU C内联asm的工作示例来尝试一下:
int foo_badasm(int n) {
int factorial = 1;
for (int i=1 ; i < n ; i++ ) {
// compile with -masm=intel, since I'm using Intel syntax here
asm volatile ("mov eax, %[x] # THIS LINE FROM INLINE ASMn"
"# more linesn"
// "xor eax,eaxn"
: // no outputs, making the volatile implicit even if we didn't specify it
: [x] "rmi" (factorial) // input can be reg, memory, or immediate
: // "eax" // uncomment this to tell the compiler we clobber eax, so our asm doesn't break step on the compiler's toes.
);
factorial *= i;
}
return factorial;
}
请参阅Godbolt编译器资源管理器上的asm输出代码,以及没有asm
语句的相同函数。
内部循环编译成这样(gcc 6.1 -O3 -fverbose-asm -masm=intel
, -fno-tree-vectorize
保持简单)。你也可以用clang试试。
.L10:
mov eax, eax # THIS LINE FROM INLINE ASM # <retval>
imul eax, edx # <retval>, i
add edx, 1 # i,
cmp edi, edx # n, i
jne .L10 #,
ret
可以看到,在这种情况下,内联asm语句产生了一个no-op。(mov eax,eax
将rax
截断为32位,将上面的32位归零,但在这个函数中它们已经是零了。)
如果我们不做其他事情,比如将寄存器置零,或者从其他来源获取mov
,我们就会破坏函数。编译器生成的asm只依赖于 asm
语句中列出的约束,而不依赖于代码的文本(不像MSVC)。
查看内联汇编标签wiki获取更多信息,特别是关于MSVC和GNU C内联汇编的区别的答案。
更重要的是,在实际使用内联asm之前,请阅读https://gcc.gnu.org/wiki/DontUseInlineAsm。
简短的回答:"你脚……别开枪!"
如果你使用asm{}
块,C/c++期望你知道你在做什么。它不会生成任何代码来"准备"您的插入,也不会"在插入之后进行清理",和它不会知道或考虑您所做的操作。在这种情况下,你会搬起石头砸自己的脚。如果您做了编译器没有预料到的、不知道的事情,并且可能会干扰编译器生成的代码正在做的事情,那么您就引入了一个您自己造成的bug。
如果你打算在汇编代码中操作寄存器,你必须采取所有适当的步骤来保持和恢复这些寄存器的状态。你必须知道你在做什么
恕我直言,最好的方法是不使用内联ASM,而是在ASM中编写一个单独的函数。
通常的步骤是:
- 用C或c++编写代码并使其正常运行。
- 打印编译器生成的程序集清单。
- 使用编译器生成的汇编代码作为基础(这使得编写调用和返回代码更容易)。
请注意,编写汇编语言来优化编译器生成的代码通常是浪费开发时间。你应该在优化之前进行分析。
另外,编写内联汇编程序是不可移植的。
- 本质:使用__128寄存器
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 在模拟器中使用并集来模拟CPU寄存器有多合适
- 使用英特尔 PIN 修改寄存器
- AVX 指令中寄存器和指针之间的客观差异
- 如何确定我的处理器有多少个 AVX 寄存器?
- 除非使用某些寄存器,否则函数挂钩会崩溃
- 寄存器上的管道计算
- 其中关于内存和寄存器的左值和右值
- 有没有办法强制C++编译器将变量存储在寄存器中?
- "变量":函数中函数作用域不允许初始化的自动或寄存器变量'naked'
- 告诉编译器我希望变量始终存储在寄存器中的正确方法是什么
- 什么是有效的寄存器变量类型
- 在矮化信息中,dw_op寄存器的确是什么意思
- 同一寄存器上的 xorp 的目的是什么
- 当我们从用户模式转移到内核模式时,哪些寄存器会发生变化?!以及转移到内核模式的原因是什么
- 什么是寄存器缓存,它与const变量有什么关系
- 编写自定义纯虚拟处理程序:调用堆栈和寄存器时的状态是什么
- 函数调用后寄存器的状态是什么
- 在c++中使用asm代码操作寄存器时会发生什么?