如果您想确保所有这些 asm 指令是连续的(它们之间没有编译器生成的代码),所有这些 asm 指令都需要在同一个 asm
语句中,并且您需要声明输入/输出/clobber 操作数,否则您将踩到编译器的寄存器。
您不能使用C 变量名lea
或mov
从 C 变量名使用或使用(除了在编译器的 asm 输出中实际定义的全局/静态符号,但即使那样,您通常也不应该这样做)。
不要使用mov
指令来设置输入,而是要求编译器使用输入操作数约束来为您完成。如果 GNU C 内联 asm 语句的第一条或最后一条指令,通常意味着您做错了并且编写了低效的代码。
顺便说一句,GNU C++ 允许 C99 样式的可变长度数组,因此howmany
可以是非const
的,甚至可以设置为不优化为常量的方式。任何可以编译 GNU 风格的内联 asm 的编译器也将支持变长数组。
如何正确编写循环
如果这看起来过于复杂,那么https://gcc.gnu.org/wiki/DontUseInlineAsm。在 asm 中编写一个独立的函数,这样您就可以只学习 asm 而不必学习 gcc 及其复杂但强大的 inline-asm 接口。您基本上必须了解 asm 并了解编译器才能正确使用它(具有正确的约束以防止在启用优化时损坏)。
请注意使用命名操作数,例如%[ptr]
代替%2
or %%ebx
。让编译器选择要使用的寄存器通常是一件好事,但对于 x86,还有一些字母不是"r"
您可以使用的,例如"=a"
专门用于 rax/eax/ax/al。请参阅https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html以及inline-assembly 标记 wiki中的其他链接。
我还习惯buf_loop%=:
在标签上附加一个唯一的数字,因此如果优化器克隆该函数或将其内联多个位置,该文件仍将组装。
Godbolt 编译器资源管理器上的源 + 编译器 asm 输出。
void ext(char *);
int foo(void)
{
int howmany = 5046; // could be a function arg
char buffer[howmany];
//ext(buffer);
const char *bufptr = buffer; // copy the pointer to a C var we can use as a read-write operand
unsigned char result;
asm("buf_loop%=: \n\t" // do {
" movb (%[ptr]), %%al \n\t" // Copy buffer[x] to al
" inc %[ptr] \n\t"
" dec %[count] \n\t"
" jnz buf_loop \n\t" // } while(ebx>0)
: [res]"=a"(result) // al = write-only output
, [count] "+r" (howmany) // input/output operand, any register
, [ptr] "+r" (bufptr)
: // no input-only operands
: "memory" // we read memory that isn't an input operand, only pointed to by inputs
);
return result;
}
我%%al
以如何显式编写寄存器名称为例:扩展 Asm(带操作数)需要双精度才能在 asm 输出中%
获取文字。%
您也可以使用%[res]
or%0
并让编译器替换%al
它的 asm 输出。(然后除非您想利用cbw
orlodsb
或类似的东西,否则您没有理由使用特定寄存器约束。) result
is unsigned char
,因此编译器将为它选择一个字节寄存器。如果你想要一个更宽的操作数的低字节,你可以使用%b[count]
例如。
这使用了一个"memory"
效率低下的clobber。您不需要编译器将所有内容都溢出到内存中,只需确保buffer[]
内存中的内容与 C 抽象机器状态相匹配。(这不能通过在寄存器中传递指向它的指针来保证)。
gcc7.2-O3
输出:
pushq %rbp
movl $5046, %edx
movq %rsp, %rbp
subq $5056, %rsp
movq %rsp, %rcx # compiler-emitted to satisfy our "+r" constraint for bufptr
# start of the inline-asm block
buf_loop18:
movb (%rcx), %al
inc %rcx
dec %edx
jnz buf_loop
# end of the inline-asm block
movzbl %al, %eax
leave
ret
没有内存破坏或输入约束,leave
出现在内联 asm 块之前,在内联 asm 使用 now-stale 指针之前释放该堆栈内存。在错误时间运行的信号处理程序会破坏它。
asm
一种更有效的方法是使用虚拟内存操作数,它告诉编译器整个数组是该语句的只读内存输入。 参见get string length in inline GNU Assembler了解更多关于这个灵活数组成员技巧的信息,它告诉编译器你在没有明确指定长度的情况下读取了所有数组。
在 C 中,您可以在强制转换中定义新类型,但在 C++ 中则不能,因此using
不是一个非常复杂的输入操作数。
int bar(unsigned howmany)
{
//int howmany = 5046;
char buffer[howmany];
//ext(buffer);
buffer[0] = 1;
buffer[100] = 100; // test whether we got the input constraints right
//using input_t = const struct {char a[howmany];}; // requires a constant size
using flexarray_t = const struct {char a; char x[];};
const char *dummy;
unsigned char result;
asm("buf_loop%=: \n\t" // do {
" movb (%[ptr]), %%al \n\t" // Copy buffer[x] to al
" inc %[ptr] \n\t"
" dec %[count] \n\t"
" jnz buf_loop \n\t" // } while(ebx>0)
: [res]"=a"(result) // al = write-only output
, [count] "+r" (howmany) // input/output operand, any register
, "=r" (dummy) // output operand in the same register as buffer input, so we can modify the register
: [ptr] "2" (buffer) // matching constraint for the dummy output
, "m" (*(flexarray_t *) buffer) // whole buffer as an input operand
//, "m" (*buffer) // just the first element: doesn't stop the buffer[100]=100 store from sinking past the inline asm, even if you used asm volatile
: // no clobbers
);
buffer[100] = 101;
return result;
}
我还使用了匹配约束,因此buffer
可以直接作为输入,并且同一寄存器中的输出操作数意味着我们可以修改该寄存器。我们foo()
通过使用const char *bufptr = buffer;
然后使用读写约束来告诉编译器该 C 变量的新值是我们留在寄存器中的值,从而获得了相同的效果。无论哪种方式,我们都会在死的 C 变量中留下一个值,该变量超出范围而不被读取,但是匹配约束方式对于您不想修改输入值的宏很有用(并且不需要您输入的类型:int dummy
也可以正常工作。)
和分配是为了表明它们都出现在 asm 中,buffer[100] = 100;
而buffer[100] = 101;
不是在 inline-asm 中合并(如果您省略"m"
输入操作数,就会发生这种情况)。IDK 为什么buffer[100] = 101;
没有优化掉;它已经死了,所以它应该是。另请注意,asm volatile
它不会阻止这种重新排序,因此它"memory"
不能替代 clobber 或使用正确的约束。