当前位置: 首页 > 面试题库 >

为什么此内联程序集不能为每个指令使用单独的asm volatile语句?

牟焱
2023-03-14
问题内容

对于以下代码:

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为stil
%rax),也让信息流经GCC还是会得到意想不到的结果。

__volatile__存在的原因是,您可以保证编译器将您的代码准确地放置在该位置……这对于此代码是 完全不必要的
保证。这是实现内存屏障等高级功能所必需的,但如果仅修改内存和寄存器,则几乎是一文不值。

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)

但是,关闭优化功能后,您对组装的了解可能会很草率。让我们尝试-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编译器进行了大量数据流分析-
因此您需要为汇编代码向编译器说明数据流。这样,编译器可以安全地操纵程序集的块,就像操纵其生成的程序集一样。

如果发现自己使用__volatile__了很多东西,则可以选择在汇编文件中编写整个函数或模块。



 类似资料:
  • 在我的应用程序中,我在menuitemclicked中添加了Observable方法,但当我点击它两次时,它仍然起作用。单次按压。方法不工作,但按钮工作良好,任何建议或方法中的任何更改。 fetch()中断; }

  • 问题内容: 我有一个expressjs应用程序,在特定的路由上,我调用了一个函数,该函数通过使用数据库文档作为参数来响应数据库中的用户。我使用基于promise的库,并且想在将数据库文档放入响应中的回调内联。但是当我这样做时程序会失败。有人可以解释为什么吗?我还想知道为什么内联调用才能真正起作用。两种方法和之间有一些根本区别吗? 这是一个有效和无效的示例。假定返回用户文档的承诺。 问题答案: 像这

  • 问题内容: 我有一小段代码每隔五分钟拍摄一次我的桌面的屏幕截图。但是,我对它占用的内存量有些困惑-通常它会爬升到200mb RAM,我敢肯定这是多余的…谁能告诉我a)减少内存占用空间的明智方法或b)它为什么涨 可言 ? 问题答案: 其他答案是正确的:Java将使用允许的尽可能多的内存,这时它将进行垃圾回收。要解决此问题,可以在JVM设置中指定较小的最大堆大小。您可以使用- Xmx设置来执行此操作。

  • RDTSC 指令的英特尔手册警告说,当 RDTSC 实际执行时,无序执行可能会更改,因此他们建议在其前面插入 CPUID 指令,因为 CPUID 将序列化指令流(CPUID 永远不会无序执行)。我的问题很简单:如果他们有能力进行指令序列化,为什么他们不进行RDTSC序列化?它的全部意义似乎是获得周期准确的时间。是否存在您不希望在它之前使用序列化指令的情况? 较新的英特尔CPU有一个单独的RDTSC

  • 问题内容: 我一直在构建具有许多不同功能的Python模块。 我正在使用Sphinx和readthedocs提供文档。我已经取得了不错的进步,但是目前我有一个庞大的页面,该页面提供了所有功能的文档(按字母顺序)。 我看过其他项目,每个项目都有单独的页面。在查看它们的源代码时,我发现已经为每个文件创建了一个单独的.rst文件。我假设这是自动完成的,并且此页面有关生成自动文档摘要的内容似乎在描述其中的

  • 我正在学习使用stl向量,这是奇怪的,这个程序不能工作。这有什么问题?如果我想用Vector实现同样的功能,应该怎么做呢?