微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

c – 为什么这个内联程序集不工作?

对于以下代码
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__错了.

__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知道的情况下.海湾合作委员会不会在集会内窥视.当我们删除__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__很多,作为替代,您可以在程序集文件中编写一个完整的函数或模块.

原文地址:https://www.jb51.cc/c/113567.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐