gpt4 book ai didi

c - 为什么这个内联汇编不能为每条指令使用单独的 asm volatile 语句?

转载 作者:IT王子 更新时间:2023-10-29 00:02:50 26 4
gpt4 key购买 nike

对于以下代码:

long buf[64];

register long rrax asm ("rax");
register long rrbx asm ("rbx");
register long rrsi asm ("rsi");

rrax = 0x34;
rrbx = 0x39;

__asm__ __volatile__ ("movq $buf,%rsi");
__asm__ __volatile__ ("movq %rax, 0(%rsi);");
__asm__ __volatile__ ("movq %rbx, 8(%rsi);");

printf( "buf[0] = %lx, buf[1] = %lx!\n", buf[0], buf[1] );

我得到以下输出:
buf[0] = 0, buf[1] = 346161cbc0!

虽然它应该是:
buf[0] = 34, buf[1] = 39!

任何想法为什么它不能正常工作,以及如何解决它?

最佳答案

您破坏内存但不告诉 GCC,因此 GCC 可以缓存 buf 中的值跨程序集调用。如果你想使用输入和输出,告诉 GCC 一切。

__asm__ (
"movq %1, 0(%0)\n\t"
"movq %2, 8(%0)"
: /* Outputs (none) */
: "r"(buf), "r"(rrax), "r"(rrbx) /* Inputs */
: "memory"); /* Clobbered */
您通常还希望让 GCC 处理大部分 mov ,寄存器选择等——即使你明确限制寄存器(rrax 仍然是 %rax )让信息流过 GCC,否则你会得到意想不到的结果。 __volatile__是错的。
原因 __volatile__存在是这样你就可以保证编译器将你的代码准确地放置在它所在的位置......这对这段代码来说是完全不必要的保证。它对于实现诸如内存屏障之类的高级功能是必要的,但如果您只是修改内存和寄存器,则几乎完全没有值(value)。
GCC 已经知道在 printf 之后它不能移动这个程序集因为 printf调用接入 buf , 和 buf可能会被议会破坏。 GCC在 rrax=0x39;之前已经知道它不能移动程序集因为 rax是汇编代码的输入。那么 __volatile__有什么用明白?没有什么。
如果您的代码在没有 __volatile__ 的情况下不起作用那么代码中有错误应该是 固定 而不是仅仅添加 __volatile__并希望这能让一切变得更好。 __volatile__关键字不是魔术,不应被视为魔术。
替代修复:
__volatile__您的原始代码需要吗?不。只需正确标记输入和破坏值。
/* The "S" constraint means %rsi, "b" means %rbx, and "a" means %rax
The inputs and clobbered values are specified. There is no output
so that section is blank. */
rsi = (long) buf;
__asm__ ("movq %%rax, 0(%%rsi)" : : "a"(rrax), "S"(rssi) : "memory");
__asm__ ("movq %%rbx, 0(%%rsi)" : : "b"(rrbx), "S"(rrsi) : "memory");
为什么 __volatile__在这里对您没有帮助:
rrax = 0x34; /* Dead code */
GCC 完全有权完全删除上述行,因为上述问题中的代码声称它从不使用 rrax .
一个更清晰的例子
long global;
void store_5(void)
{
register long rax asm ("rax");
rax = 5;
__asm__ __volatile__ ("movq %%rax, (global)");
}
拆卸或多或少如您所料 -O0 ,
movl $5, %rax
movq %rax, (global)
但是在关闭优化的情况下,您可能对 assembly 相当草率。让我们试试 -O2 :
movq %rax, (global)
哎呀!哪里去了 rax = 5;去?这是死代码,因为 %rax从未在函数中使用——至少就 GCC 而言是这样。 GCC 不会查看程序集内部。当我们删除 __volatile__ 时会发生什么?
; empty
好吧,你可能会想 __volatile__通过防止 GCC 丢弃您宝贵的程序集来为您提供服务,但这只是掩盖了 GCC 认为您的程序集没有做任何事情的事实。 GCC 认为你的程序集不接受输入,不产生输出,并且不破坏内存。你最好把它弄直:
long global;
void store_5(void)
{
register long rax asm ("rax");
rax = 5;
__asm__ __volatile__ ("movq %%rax, (global)" : : : "memory");
}
现在我们得到以下输出:
movq %rax, (global)
更好的。但是如果你告诉 GCC 有关输入的信息,它会确保 %rax首先正确初始化:
long global;
void store_5(void)
{
register long rax asm ("rax");
rax = 5;
__asm__ ("movq %%rax, (global)" : : "a"(rax) : "memory");
}
输出,经过优化:
movl $5, %eax
movq %rax, (global)
正确的!我们甚至不需要使用 __volatile__ .
为什么 __volatile__存在? __volatile__ 的主要正确用法是如果您的汇编代码除了输入、输出或破坏内存之外还执行其他操作。也许它会混淆 GCC 不知道的特殊寄存器,或者影响 IO。您在 Linux 内核中经常看到它,但它在用户空间中经常被误用。 __volatile__关键字非常诱人,因为我们 C 程序员常常认为我们几乎已经在用汇编语言编程了。不是。 C 编译器会进行大量数据流分析——因此您需要向编译器解释汇编代码的数据流。这样,编译器就可以安全地操作您的程序集 block ,就像它操作它生成的程序集一样。
如果您发现自己在使用 __volatile__很多,作为替代方案,您可以在程序集文件中编写整个函数或模块。

关于c - 为什么这个内联汇编不能为每条指令使用单独的 asm volatile 语句?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8891139/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com