【问题标题】:Inline assembly inside loops循环内的内联汇编
【发布时间】:2010-01-13 10:14:18
【问题描述】:

我在一个项目中大量使用内联汇编,在该项目中我需要在编译时调用具有未知数量参数的函数,而我自己管理自己让它工作,有时,在 linux 中(在 windows 中我不记得有这个问题)发生了这样的奇怪事情:

如果我有类似的东西

for(int i = 1; i >= 0; i--)
   asm("push %0"::"m"(someArray[i]));

有效。

如果我有

for(int i = this->someVar; i >= 0; i--)
   asm("push %0"::"m"(someArray[i]));

我用我的生命保证 someVar 保持值 1 它会引发分段错误。

如果我有

int x = 1;
for(int i = x; i >= 0; i--)
   asm("push %0"::"m"(someArray[i]));

它可以工作,但是

int x = this->someVar;
for(int i = x; i >= 0; i--)
    asm("push %0"::"m"(someArray[i]));

没有。

另外,奇怪的是,我可以说,虽然在某些函数中我没有遇到问题,但在我拥有的其他函数中,都在同一个对象中。

如果有人可以向我指出一些可以解决问题所在的信息,我将不胜感激。

请注意,我确实必须在 for 循环中推送参数,因此避免它不是一种选择。

我也尝试使用内联汇编词“volatile”,但没有任何改变。

【问题讨论】:

  • 问题肯定是,“为什么不将指针传递给数组而不是将数组元素复制到堆栈中”,或者我错过了什么?沿着: func (someArray, this->someVar) 然后你也会得到性能提升(没有内存移动,没有循环)
  • 我正在调用我无法控制的函数。他们在其他图书馆。我需要按照他们的期望将参数传递给他们。
  • 通过在编译器的手下执行不成对的push 来改变局部变量的相对位置是非常非常糟糕的主意。
  • 你的意思是:“你在做什么是一个非常糟糕的主意?”。老实说,我从 11 月开始学习 C++ 和汇编,所以我在这两方面都是新手。

标签: c++ inline-assembly


【解决方案1】:

我不明白问题出在哪里,但尝试使用相同的清晰 asm 代码编写代码

asm{
   loop1:
     mov ax, this->var
     ...
     dec ax
     cmp ax, 0
     je exit
     jmp loop1
}

...

退出:

还可以尝试将“var”值设为静态可能也有帮助。

【讨论】:

  • 它不可能是静态的,因为代码是如何流动的。我将尝试使其成为仅汇编代码。谢谢。
【解决方案2】:

检查反汇编。最可能的原因是i 和/或保存最终值的变量在for 循环的每次迭代中从堆栈上的固定偏移量重新获取,并且您的push 偏移堆栈指针的位置编译器期望它是这样,因此会导致获取错误的值。

您可以尝试各种解决方法(例如,声明局部变量 register),但不幸的是,在这种情况下,没有很好的方法保证正确的 C/C++ 行为。为避免此问题,请按照 oivoodoo 的建议自行实现循环。

【讨论】:

  • 感谢我将尝试 oivoodoo 的建议
【解决方案3】:

这是我的心理调试工作:

ithis 最有可能存储在堆栈中,并且在 386 及更高版本上,机器代码可以直接引用 esp-相对内存位置,因此编译器可能会产生类似的指令

mov eax,[esp+8]

this 的值放入eax 寄存器。问题是您的push 操作与堆栈指针混淆,因此这些硬编码访问将在第一次迭代后访问(越来越多)错误的内存位置。

最有可能的是,没有this->someVar 的更简单的循环形式会被编译器更彻底地优化,并导致机器代码只使用寄存器并且没有esp-relative 访问,这意味着它们可以继续正常工作。

曾几何时,对局部变量和参数的所有内存访问都是通过ebp 寄存器完成的,您的内联汇编代码不会更改该寄存器。如果您能找到一个编译器开关来强制使用ebp 而不是esp,这可能会解决您的问题。

警告:编译器不希望你弄乱堆栈——它希望它始终知道堆栈顶部的位置。如果你真的想动态地将东西压入堆栈,我建议你完全用汇编语言编写循环本身,就像oivoodoo 所做的那样。

【讨论】:

  • 虽然我不知道你对这个主题的了解,但我的观察和我所知道的让我认为正在发生的事情就像你所说的那样。感谢您的有用评论。
【解决方案4】:

首先,可能发生的情况是 linux 下的 gcc 正在使用堆栈指针来索引您的局部变量,而不是使用堆栈帧指针。这是一个优化,允许 gcc 使用帧指针(x86 下的 BP)作为另一个通用寄存器,并避免大量设置帧的代码。帧本质上只是属于局部功能的 SP 和 BP 之间的区域。我敢打赌,如果你包含一个对 alloca 的调用,并且你传递给这个函数的大小会变得更好,因为它会强制编译器不做这个优化。

话虽如此,错误确实存在于您的代码中。除非你真的知道你在做什么,否则你永远不应该退出一个内联汇编,它的堆栈指针与你进入内联汇编时的堆栈指针不同。编译器几乎总是认为他们完全拥有堆栈指针。他们依赖于它保持不变,以便他们可以使用它来找到他们存储变量的位置。 您还应该远离帧指针 (BP)。

很少有可以处理这些问题的情况,通常用于上下文切换代码(从一个线程或进程更改为另一个)之类的事情。

【讨论】:

    【解决方案5】:

    如果您知道 args 数量的限制,您可以使用具有该数量参数的单个函数调用来调用它,将您的实际参数对齐到最后。

    警告:x86_64 abi 对某些参数使用寄存器,这也破坏了这个和您的代码。

    【讨论】:

    • 数量是无限的,我希望它是这样的。我想您的解决方案效率会降低。关于你提到的x86_64问题,这不是因为应用程序不适用于它。
    猜你喜欢
    • 2011-06-09
    • 2012-10-20
    • 1970-01-01
    • 2013-08-28
    • 2013-07-23
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 2019-05-11
    相关资源
    最近更新 更多