46

我记得看到过一种使用扩展 gcc 内联汇编来读取寄存器值并将其存储到 C 变量中的方法。

尽管我一生都无法记住如何形成 asm 语句。

4

8 回答 8

35

编者注:这种使用本地 register-asm 变量的方式现在被 GCC 记录为“不支持”。它通常仍然可以在 GCC 上工作,但会因 clang 而中断。(我认为,在发布此答案后添加了文档中的此措辞。)

全局固定寄存器变量版本对于 32 位 x86 的性能开销较大,只有 7 个 GP 整数寄存器(不包括堆栈指针)。这会将其减少到 6。仅当您有一个所有代码都大量使用的全局变量时才考虑这一点。


到目前为止,与其他答案的方向不同,因为我不确定你想要什么。

GCC 手册§ 5.40 指定寄存器中的变量

register int *foo asm ("a5");

a5是应该使用的寄存器的名称……</p>

自然地,寄存器名称是依赖于 cpu 的,但这不是问题,因为特定的寄存器通常对显式汇编指令有用(参见扩展汇编)。这两件事通常都要求您根据 cpu 类型对程序进行条件化。

定义这样一个寄存器变量不会保留寄存器;在流量控制确定变量值不存在的地方,它仍然可用于其他用途。

GCC 手册 § 3.18 代码生成约定选项

-ffixed-注册

将名为reg的寄存器视为固定寄存器;生成的代码不应该引用它(除了可能作为堆栈指针、帧指针或其他一些固定角色)。

这可以以更简单的方式复制理查德的答案,

int main() {
    register int i asm("ebx");
    return i + 1;
}

尽管这毫无意义,因为您不知道ebx注册表中的内容。

如果你将这两者结合起来,用 , 编译gcc -ffixed-ebx

#include <stdio.h>
register int counter asm("ebx");
void check(int n) {
    if (!(n % 2 && n % 3 && n % 5)) counter++;
}
int main() {
    int i;
    counter = 0;
    for (i = 1; i <= 100; i++) check(i);
    printf("%d Hamming numbers between 1 and 100\n", counter);
    return 0;
}

您可以确保 C 变量始终使用驻留在寄存器中以便快速访问,并且不会被其他生成的代码破坏。(方便的是,ebx在通常的 x86 调用约定下被调用者保存,所以即使它被调用不使用 编译的其他函数所破坏-ffixed-*,它也应该被恢复。)

另一方面,这绝对不是可移植的,通常也不是性能优势,因为您限制了编译器的自由。

于 2010-01-22T04:26:11.810 回答
22

这是获取ebx的一种方法:

int main()
{
    int i;
    asm("\t movl %%ebx,%0" : "=r"(i));
    return i + 1;
}

结果:

main:
    subl    $4, %esp
    #APP
             movl %ebx,%eax
    #NO_APP
    incl    %eax
    addl    $4, %esp
    ret


编辑:

"=r"(i) 是一个输出约束,告诉编译器第一个输出 (%0) 是一个寄存器,应该放在变量 "i" 中。在这个优化级别(-O5),变量 i 永远不会存储到内存中,而是保存在 eax 寄存器中,它也恰好是返回值寄存器。

于 2010-01-22T01:05:33.280 回答
6

我不知道 gcc,但在 VS 中是这样的:

int data = 0;   
__asm
{
    mov ebx, 30
    mov data, ebx
}
cout<<data;

本质上,我将数据移动ebx到您的变量data中。

于 2010-01-22T00:55:30.087 回答
5

这会将堆栈指针寄存器移动到 sp 变量中。

intptr_t sp;
asm ("movl %%esp, %0" : "=r" (sp) );

只需将 'esp' 替换为您感兴趣的实际寄存器(但确保不要丢失 %%),并将 'sp' 替换为您的变量。

于 2010-01-22T01:07:19.313 回答
2

从 GCC 文档本身: http: //gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

于 2010-01-22T17:40:18.543 回答
2
#include <stdio.h>

void gav(){
        //rgv_t argv = get();
        register unsigned long long i asm("rax");
        register unsigned long long ii asm("rbx");
        printf("I`m gav - first arguman is: %s - 2th arguman is: %s\n", (char *)i, (char *)ii);
}

int main(void)
{
    char *test = "I`m main";
    char *test1 = "I`m main2";
    printf("0x%llx\n", (unsigned long long)&gav);
    asm("call %P0" : :"i"((unsigned long long)&gav), "a"(test), "b"(test1));
    return 0;
}
于 2018-03-17T09:36:28.373 回答
2

当您的内联语句运行时,您无法知道编译器生成的代码将在任何寄存器中存储什么值asm,因此该值通常是没有意义的,您最好使用调试器在停止时查看寄存器值断点。

话虽这么说,如果你要完成这个奇怪的任务,你还不如高效地完成它。

在某些目标(如 x86)上,您可以使用特定寄存器输出约束来告诉编译器输出将在 哪个寄存器中。使用带有空 asm 模板(零指令)的特定寄存器输出约束来告诉编译器您的 asm语句不关心输入时的寄存器值,但之后给定的 C 变量将在该寄存器中。

#include <stdint.h>

int foo() {
    uint64_t rax_value;           // type width determines register size
    asm("" : "=a"(rax_value));  // =letter determines which register (or partial reg)

    uint32_t ebx_value;
    asm("" : "=b"(ebx_value));

    uint16_t si_value;
    asm("" : "=S"(si_value) );

    uint8_t sil_value;  // x86-64 required to use the low 8 of a reg other than a-d
       // With -m32:  error: unsupported size for integer register
    asm("# Hi mom, my output constraint picked %0" : "=S"(sil_value) );

    return sil_value + ebx_value;
}

在Godbolt 上为 x86-64使用 clang5.0编译。请注意,2 个未使用的输出值已被优化掉,没有#APP/#NO_APP编译器生成的 asm-comment 对(将汇编器切换到 / 进入快速解析模式,或者至少习惯于,如果不再是这样的话)。这是因为我没有使用asm volatile,而且它们有一个输出操作数,所以它们不是隐式volatile的。

foo():                                # @foo()
# BB#0:
    push    rbx
    #APP
    #NO_APP
    #DEBUG_VALUE: foo:ebx_value <- %EBX
    #APP
    # Hi mom, my output constraint picked %sil
    #NO_APP
    #DEBUG_VALUE: foo:sil_value <- %SIL
    movzx   eax, sil
    add     eax, ebx
    pop     rbx
    ret
                                    # -- End function
                                    # DW_AT_GNU_pubnames
                                    # DW_AT_external

注意编译器生成的代码直接从指定的寄存器将两个输出相加。还要注意 RBX 的推送/弹出,因为 RBX 是 x86-64 System V 调用约定中的调用保留寄存器。(基本上所有 32 位和 64 位 x86 调用约定)。但是我们已经告诉编译器我们的 asm 语句在那里写了一个值。(使用空的 asm 语句是一种 hack;没有语法可以直接告诉编译器我们只想读取一个寄存器,因为就像我说的那样,当您的 asm 语句是时,您不知道编译器对寄存器做了什么插入。)

编译器会将您的 asm 语句视为它实际编写了该寄存器,因此如果它需要该值以供以后使用,它将在您的 asm 语句“运行”时将其复制到另一个寄存器(或溢出到内存)。


其他x86 寄存器约束b(bl/bx/ebx/rbx), c(.../rcx), d(.../rdx), S(sil/si/esi/rsi), D(.../rdi)。bpl/bp/ebp/rbp 没有特定的约束,即使它在没有帧指针的函数中并不特殊。(也许是因为使用它会使你的代码不编译-fno-omit-frame-pointer。)

您可以使用register uint64_t rbp_var asm ("rbp"), 在这种情况下asm("" : "=r" (rbp_var)); 保证"=r"约束将被选中rbp。同样对于 r8-r15,它也没有任何显式约束。在某些架构上,如 ARM,asm-register 变量是指定 asm 输入/输出约束所需的寄存器的唯一方法。(请注意,asm 约束是唯一受支持的register asmvariables使用;不能保证该变量的值在其他任何时候都会在该寄存器中。


没有什么可以阻止编译器将这些 asm 语句放置在函数(或内联后的父函数)中它想要的任何位置。因此,您无法控制在何处对寄存器的值进行采样。 asm volatile可以避免一些重新排序,但可能只针对其他volatile访问。您可以检查编译器生成的 asm 以查看是否得到了所需的内容,但请注意它可能是偶然的,以后可能会中断。

您可以在依赖链中放置一条 asm 语句,以控制编译器放置它的位置。使用"+rm"约束来告诉编译器它修改了一些其他变量,这些变量实际上用于不优化的东西。

uint32_t ebx_value;
asm("" : "=b"(ebx_value), "+rm"(some_used_variable) );

wheresome_used_variable可能是一个函数的返回值,并且(经过一些处理)作为 arg 传递给另一个函数。或者在循环中计算,并将作为函数的返回值返回。在这种情况下,asm 语句保证在循环结束之后的某个时间点出现,并且在任何依赖于该变量的后续值的代码之前。

不过,这将破坏该变量的常量传播等优化。 https://gcc.gnu.org/wiki/DontUseInlineAsm。编译器不能对输出值做任何假设;它不检查asm语句是否有零指令。


这不适用于 gcc 不允许您用作输出操作数或破坏器的某些寄存器,例如堆栈指针。

但是,如果您的程序对堆栈执行一些特殊操作,则将值读入 C 变量可能对堆栈指针有意义。

作为 inline-asm 的替代方法,__builtin_frame_address(0)可以获取堆栈地址。(但是 IIRC 会导致该函数生成一个完整的堆栈帧,即使在-fomit-frame-pointer启用时也是如此,就像 x86 上的默认设置一样。)

尽管如此,在许多几乎免费的函数中(并且制作堆栈帧可能有利于代码大小,因为 RBP-relative 的寻址模式比 RSP-relative 访问局部变量的寻址模式更小)。

在语句中使用mov指令asm当然也可以。

于 2018-03-17T10:42:08.473 回答
-1

不是你要找的吗?

句法:

 asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
于 2010-01-22T00:51:24.893 回答