我记得看到过一种使用扩展 gcc 内联汇编来读取寄存器值并将其存储到 C 变量中的方法。
尽管我一生都无法记住如何形成 asm 语句。
我记得看到过一种使用扩展 gcc 内联汇编来读取寄存器值并将其存储到 C 变量中的方法。
尽管我一生都无法记住如何形成 asm 语句。
编者注:这种使用本地 register-asm 变量的方式现在被 GCC 记录为“不支持”。它通常仍然可以在 GCC 上工作,但会因 clang 而中断。(我认为,在发布此答案后添加了文档中的此措辞。)
全局固定寄存器变量版本对于 32 位 x86 的性能开销较大,只有 7 个 GP 整数寄存器(不包括堆栈指针)。这会将其减少到 6。仅当您有一个所有代码都大量使用的全局变量时才考虑这一点。
到目前为止,与其他答案的方向不同,因为我不确定你想要什么。
register int *foo asm ("a5");
这
a5
是应该使用的寄存器的名称……</p>自然地,寄存器名称是依赖于 cpu 的,但这不是问题,因为特定的寄存器通常对显式汇编指令有用(参见扩展汇编)。这两件事通常都要求您根据 cpu 类型对程序进行条件化。
定义这样一个寄存器变量不会保留寄存器;在流量控制确定变量值不存在的地方,它仍然可用于其他用途。
-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-*
,它也应该被恢复。)
另一方面,这绝对不是可移植的,通常也不是性能优势,因为您限制了编译器的自由。
这是获取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 寄存器中,它也恰好是返回值寄存器。
我不知道 gcc,但在 VS 中是这样的:
int data = 0;
__asm
{
mov ebx, 30
mov data, ebx
}
cout<<data;
本质上,我将数据移动ebx
到您的变量data
中。
这会将堆栈指针寄存器移动到 sp 变量中。
intptr_t sp;
asm ("movl %%esp, %0" : "=r" (sp) );
只需将 'esp' 替换为您感兴趣的实际寄存器(但确保不要丢失 %%),并将 'sp' 替换为您的变量。
从 GCC 文档本身: http: //gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
#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;
}
当您的内联语句运行时,您无法知道编译器生成的代码将在任何寄存器中存储什么值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 asm
variables使用;不能保证该变量的值在其他任何时候都会在该寄存器中。
没有什么可以阻止编译器将这些 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
当然也可以。