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 

而不是你的代码中的lealea只能使用“普通”编译时大小的数组,但不适用于运行时大小的数组。

另一个问题是你的代码中是否真的需要一个运行时大小的数组。 可能是你错误地做到了吗? 如果您只是将howmany声明更改为

 const int howmany = 5046; 

数组将变为“普通”C ++数组,您的代码可能会按原样开始工作(即使用lea )。

所有这些asm指令都需要在同一个 asm语句中,如果你想确定它们是连续的(它们之间没有编译器生成的代码),你需要声明输入/输出/ clobber操作数,否则你将踩到编译器的寄存器。

你不能将leamov用于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 。 (然后你没有理由使用特定的寄存器约束,除非你想利用cbwlodsb或类似的东西。) resultunsigned 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或使用正确的约束。