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