0

我的代码

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 编译器。出于某种原因,我的缓冲区/多少变量在我的 asm 中是未定义的。我不确定为什么。我只想将缓冲区数组的起始地址移动到 esi 寄存器中,在将每个元素复制到 al 寄存器时循环它“多少”次。

4

3 回答 3

7

你在 gcc 中使用内联汇编器吗?(如果不是,在其他 C++ 编译器中,究竟是什么?)

如果是 gcc,请参阅此处的详细信息,尤其是此示例:

    asm ("leal (%1,%1,4), %0"
         : "=r" (five_times_x)
         : "r" (x) 
         );

%0并且%1指的是 C 级变量,它们被专门列为asm. 在您的示例中,您只有“输入”,因此您将有一个空的第二个操作数(传统上,在该冒号之后使用注释,例如/* no output registers */,更明确地表示)。

于 2009-12-24T02:47:51.350 回答
1

声明这样一个数组的部分

int howmany = 5046;
char buffer[howmany];

不是有效的 C++。在 C++ 中,不可能声明具有“可变”或运行时大小的数组。在 C++ 数组声明中,大小始终是编译时常量。

如果您的编译器允许此数组声明,则意味着它将其实现为扩展。在这种情况下,您必须自己进行研究以弄清楚它如何在内部实现这样一个运行时大小的数组。我猜想内部buffer将被实现为指针,而不是真正的数组。如果我的猜测是正确的并且它确实是一个指针,那么将数组地址加载到其中的正确方法esi可能是

mov buffer,%esi

而不是 a lea,就像您的代码中那样。lea仅适用于“正常”编译时大小的数组,但不适用于运行时大小的数组。

另一个问题是你的代码中是否真的需要一个运行时大小的数组。莫非是你自己弄错了?如果您只是将howmany声明更改为

const int howmany = 5046;

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

于 2009-12-24T02:49:25.037 回答
1

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

您不能使用C 变量名leamov从 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]代替%2or %%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 输出。(然后除非您想利用cbworlodsb或类似的东西,否则您没有理由使用特定寄存器约束。) resultis 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 或使用正确的约束。

于 2017-11-17T19:52:35.130 回答