at&t asm inline c ++问题
我的代码
const int howmany = 5046; char buffer[howmany]; asm("lea buffer,%esi"); //Get the address of buffer asm("mov howmany,%ebx"); //Set the loop number asm("buf_loop:"); //Lable for beginning of loop asm("movb (%esi),%al"); //Copy buffer[x] to al asm("inc %esi"); //Increment buffer address asm("dec %ebx"); //Decrement loop count asm("jnz buf_loop"); //jump to buf_loop if(ebx>0)
我的问题
我正在使用gcc编译器。 出于某种原因,我的缓冲区/ howmany变量在我的asm眼中是不确定的。 我不知道为什么。 我只想将缓冲区数组的起始地址移动到esi寄存器中,将每个元素复制到al寄存器时循环“howmany”次。
你在gcc中使用内联汇编程序吗? (如果不是,在其他C ++编译器中,究竟是什么?)
如果是gcc,请参阅此处的详细信息,特别是此示例:
asm ("leal (%1,%1,4), %0" : "=r" (five_times_x) : "r" (x) );
%0
和%1
指的是C级变量,它们被特别列为asm
的第二个(用于输出)和第三个(用于输入)参数。 在您的示例中,您只有“输入”,因此您将拥有一个空的第二个操作数(传统上,在该冒号后使用注释,例如/* no output registers */
,以更明确地指示)。
声明像这样的数组的部分
int howmany = 5046; char buffer[howmany];
是无效的C ++。 在C ++中,不可能声明具有“变量”或运行时大小的数组。 在C ++数组声明中,大小始终是编译时常量。
如果您的编译器允许此数组声明,则意味着它将其实现为扩展。 在这种情况下,您必须自己进行研究,以弄清楚它是如何在内部实现这样一个运行时大小的数组。 我猜想内部buffer
将被实现为指针 ,而不是真正的数组。 如果我的猜测是正确的并且它确实是一个指针,那么将数组的地址加载到esi
的正确方法可能是
mov buffer,%esi
而不是你的代码中的lea
。 lea
只能使用“普通”编译时大小的数组,但不适用于运行时大小的数组。
另一个问题是你的代码中是否真的需要一个运行时大小的数组。 可能是你错误地做到了吗? 如果您只是将howmany
声明更改为
const int howmany = 5046;
数组将变为“普通”C ++数组,您的代码可能会按原样开始工作(即使用lea
)。
所有这些asm指令都需要在同一个 asm
语句中,如果你想确定它们是连续的(它们之间没有编译器生成的代码),你需要声明输入/输出/ clobber操作数,否则你将踩到编译器的寄存器。
你不能将lea
或mov
用于C变量名称 (除了在编译器的asm输出中实际定义的全局/静态符号之外,但通常你不应该这样做)。
而不是使用mov
指令来设置输入,请求编译器使用输入操作数约束为您执行此操作。 如果GNU C内联asm语句的第一个或最后一个指令,通常意味着你做错了并编写了低效的代码。
而BTW,GNU C ++允许使用C99风格的可变长度数组,因此允许使用非常const
,甚至设置为不会优化为常量的方式。 任何可以编译GNU样式的内联asm的编译器也将支持可变长度数组。
如何正确编写循环
如果这看起来过于复杂,那么请https://gcc.gnu.org/wiki/DontUseInlineAsm 。 在asm中编写一个独立的函数,这样你就可以学习asm,而不必学习gcc及其复杂但强大的inline-asm接口。 您基本上必须知道asm并理解编译器才能正确使用它(使用正确的约束来防止在启用优化时出现断开)。
请注意使用命名操作数,如%[ptr]
而不是%2
或%%ebx
。 让编译器选择使用哪个寄存器通常是件好事,但对于x86,你可以使用除"r"
以外的字母,比如rax / eax / ax / al的"=a"
。 请参阅https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html ,以及内联汇编代码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(带有操作数)需要double %
来获得asm输出中的文字%
。 您还可以使用%[res]
或%0
并让编译器在其asm输出中替换%al
。 (然后你没有理由使用特定的寄存器约束,除非你想利用cbw
或lodsb
或类似的东西。) result
是unsigned char
,所以编译器会为它选择一个字节寄存器。 如果你想要更宽的操作数的低字节,你可以使用%b[count]
作为例子。
这使用了"memory"
破坏,这是低效的 。 您不需要编译器将所有内容溢出到内存中,只是为了确保内存中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
如果没有内存clobber或输入约束,则在内联asm块之前出现leave
,在内联asm使用now-stale指针之前释放该堆栈内存。 在错误的时间运行的信号处理程序会破坏它。
更有效的方法是使用虚拟内存操作数,它告诉编译器整个数组是asm
语句的只读内存输入。 有关此灵活数组成员技巧的更多信息,请参阅内联GNU汇编程序中的获取字符串长度,以告知编译器您读取所有数组而不明确指定长度。
在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
可以直接作为输入,同一寄存器中的输出操作数意味着我们可以修改该寄存器。 我们通过使用const char *bufptr = buffer;
在foo()
获得相同的效果const char *bufptr = buffer;
然后使用读写约束告诉编译器该C变量的新值是我们在寄存器中留下的值。 无论哪种方式,我们将一个值留在一个超出范围的死C变量中而不被读取,但匹配的约束方式对于您不想修改输入值的宏(并且不需要你输入的类型: int dummy
也可以正常工作。)
buffer[100] = 100;
和buffer[100] = 101;
赋值是为了表明它们都出现在asm中,而不是在inline-asm中合并(如果省略"m"
输入操作数就会发生这种情况)。 IDK为什么buffer[100] = 101;
没有优化; 它应该是死的。 另请注意, asm volatile
不会阻止此重新排序,因此它不能替代"memory"
clobber或使用正确的约束。