簡體   English   中英

at&t asm內聯C ++問題

[英]at&t asm inline c++ problem

我的密碼

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看不到我的buffer / howmany變量的定義。 我不知道為什么。 我只想將緩沖區數組的起始地址移入esi寄存器,將其循​​環“多次”,同時將每個元素復制到al寄存器。

您是否在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語句中,並且您需要聲明input / output / clobber操作數,否則您將踩到編譯器的寄存器。

您不能在C變量名稱中使用leamov (從全局或靜態符號中除外,它們實際上是在編譯器的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%%ebx 通常讓編譯器選擇要使用的寄存器是一件好事,但是對於x86,您可以使用除"r"以外的其他字母,例如專門用於rax / eax / ax / al的"=a" 請參閱https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html ,以及inline-assembly標簽Wiki中的其他鏈接。

我還使用了buf_loop%=:在標簽上附加了一個唯一的數字,因此,如果優化程序克隆該函數或將其內聯到多個位置,則文件仍會匯編。

Godbolt編譯器資源管理器上的 Source +編譯器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

在沒有內存破壞者或輸入約束的情況下, leave出現嵌入式asm塊之前,在嵌入式asm使用當前失效的指針之前釋放該堆棧內存。 在錯誤的時間運行的信號處理程序會破壞它。


一種更有效的方法是使用虛擬內存操作數,該操作數告訴編譯器整個數組是asm語句的只讀內存輸入。 有關此flexible-array-member技巧的更多信息,請參見內聯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"破壞"memory"的替代方案,也不是使用正確的約束條件的替代方案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM