使用GCC的内联汇编来学习汇编的问题在于,你花了一半的时间来学习gcc的内联汇编如何工作,而不是真正学习汇编。例如,我可以这样编写相同的代码:
#include <stdio.h>
int getStringLength(const char *pStr){
int len;
__asm__ (
"repne scasb\n\t"
"not %%ecx\n\t"
"dec %%ecx"
:"=c" (len), "+D"(pStr) /*Outputs*/
:"c"(-1), "a"(0) /*Inputs*/
/* tell the compiler we read the memory pointed to by pStr,
with a dummy input so we don't need a "memory" clobber */
, "m" (*(const struct {char a; char x[];} *) pStr)
);
return len;
}
查看编译器的asm输出在 Godbolt 编译器资源管理器上 https://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,source:'%23include+%3Cstdio.h%3E%0A%0Aint+getStringLength(const+char+*pStr)%7B%0A%0A+++++int+len%3B%0A%0A+++++__asm__++(%0A+++++++++%22repne+scasb%5Cn%5Ct%22%0A+++++++++%22not+%25%25ecx%5Cn%5Ct%22%0A+++++++++%22dec+%25%25ecx%22%0A+++++++++:%22%3Dc%22+(len),+%22%2BD%22(pStr)%0A+++++++++:%22c%22(-1),+%22a%22(0)%0A%23if+0%0A+++++++++//+works+for+clang+(not+gcc)%0A+++++++++,+%22m%22(*pStr)%0A%23elif+1%0A+++++++++//+works+for+gcc+and+clang%0A+++++++++,+%22m%22+(*(const+struct+%7Bchar+a%3B+char+x%5B%5D%3B%7D+*)+pStr)%0A%23else%0A+++++++++//+nothing.++Works+only+for+ICC%0A%23endif%0A+++++)%3B%0A%0A+++++return+len%3B%0A%7D%0A%0Achar+buff%5B50%5D+%3D+%22hello+world%22%3B%0A%0Aint+foo()%0A%7B%0A++++buff%5B4%5D+%3D+0%3B+++++//+Can+be+optimized+away+if+the+compiler+doesn!'t+think+the+asm+reads+buff%5B4%5D%0A++++int+a+%3D+getStringLength(buff)%3B%0A++++buff%5B4%5D+%3D+1%3B+++++//+because+this+store+makes+the+%3D+0+store+%22dead%22.%0A%0A++++//printf(%22%25s:+%25d%5Cn%22,+buff,+a)%3B%0A++++return+a%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:33.46801346801348,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:g72,filters:(b:'0',commentOnly:'0',directives:'0'),options:'-xc+-std%3Dgnu11+-Wall+-O3+',source:1),l:'5',n:'0',o:'x86-64+gcc+7.2+(Editor+%231,+Compiler+%231)',t:'0')),k:33.198653198653204,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:clang400,filters:(b:'0',commentOnly:'0',directives:'0'),options:'-xc+-std%3Dgnu11+-Wall+-O3',source:1),l:'5',n:'0',o:'x86-64+clang+4.0.0+(Editor+%231,+Compiler+%232)',t:'0')),k:33.33333333333333,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',m:100,n:'0',o:'',t:'0')),version:4。虚拟内存输入是棘手的部分:请参阅评论中的讨论和在海湾合作委员会邮件列表上 https://gcc.gnu.org/ml/gcc/2017-08/msg00120.html寻找最优化的方法来做到这一点,而且仍然是安全的。
将此与您的示例进行比较
- 我不初始化
len
,因为 asm 将其声明为输出 (=c)。
- 没有必要复制
pStr
因为它是一个局部变量。根据规范,我们已经被允许更改它(尽管因为它是const
我们不应该修改它指向的数据)。
- 没有理由告诉内联汇编放置
Ptr
in eax
,只是让你的汇编将其移动到edi
。我只是将值放入edi
首先。请注意,由于值edi
正在改变,我们不能仅仅将它声明为“输入”(根据规范,内联汇编不能改变输入的值)。将其更改为读/写输出可以解决此问题。
- 不需要将 asm 设为 0
eax
,因为你可以让约束为你做这件事。作为一个附带的好处,gcc 将“知道”它在eax
注册,并且(在优化版本中)它可以重用它(想想:检查 2 个字符串的长度)。
- 我可以使用约束来初始化
ecx
也。如前所述,不允许更改输入值。但既然我定义了ecx
作为输出,gcc 已经知道我正在更改它。
- 由于 ecx、eax 和 edi 的内容均已显式指定,因此无需再破坏任何内容。
所有这些都使得代码(稍微)更短、更高效。
但这是荒谬的。你到底怎么知道这一切(我可以说“哎呀”吗?)?
如果目标是学习 asm,那么使用内联 asm 并不是最好的方法(事实上,我想说内联 asm 是一种bad idea https://gcc.gnu.org/wiki/DontUseInlineAsm在多数情况下)。我建议您将 getStringLength 声明为 extern 并将其完全编写在 asm 中,然后将其与您的 C 代码链接。
通过这种方式,您可以了解参数传递、返回值、保留寄存器(以及了解哪些寄存器必须保留以及可以安全地用作暂存器)、堆栈帧、如何将 asm 与 C 链接等等。这比了解内联汇编的官样文章更有用。