用C语言表达汇编指令

C语言 专栏收录该内容
2 篇文章 0 订阅

谢谢tsahoo在论坛中为大家提供的原文 ,需要翻译帮助的朋友请把原文贴在这里

在汇编语言中使用asm,你也可以使用C语句表达。也就是说你不需要知道你要使用的数据在寄存器或存储器中的位置。
你需要描述汇编指令象出现在机器中的那样,为每一个操作数加上一个强制的约束。
例如,这是使用68881's fsinx指令的方法: 
      asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
这 是用C语言表达当结果为输出操作数时的输入操作数。每个都有“f”如它的操作数约束,也就是说需要浮点记录器。显示了操作数为输出;所有的输出操作数必须 使用“=”。约束在机器中使用相同的语句(见约束)。每个操作数都被括号中在C语句之后的操作约束字符串所描述。一个冒号把第一个输出操作数分开还有如果 存在最后一个的话同样这样处理。逗号在每个组中分隔开操作数。操作数的总和一般被限制在301以内;这个限制可能在GCC的将来版本中被打破。如果没有输 出而只有输入的话,那么需要在输出处加两个连续的冒号。
如 GCC 3.1 版,它也可能使用在汇编码中可以参考的符号名来指定输入和输出操作数。这些名字在在约束字符串前的方括号内被指定,并且可以在汇编码中使用%[name]代替操作数后的百分标记。使用命名操作数的例子如下:
     asm ("fsinx %[angle],%[output]"
          : [output] "=f" (result)
          : [angle] "f" (angle));
注意符号操作数的名字与其他C语言识别符没有什么联系。你可以使用任何你喜欢的名字,甚至那些现在有的标号,但是必须确保在同一个汇编程序中没有两个相同的符号名出现。
输 出操作表达必须为左值; 编译器才能识别它。输入操作数则不需要这样。编译器不能识别操作数对指令是否有合理的数据类型。它不能解析汇编程序指令并且不知道汇编指令的含义,甚至不 知道是否为有效的汇编指令输入。asm经常被用在自身不知道是否存在的机器指令编译中。如果输出操作表达不能直接寻址(例如:bit-field),那么 约束中必须允许寄存器。在这种情况下,GCC将使用寄存器作为asm 的输出,然后存储那个寄存器到输出内。
当约束为读写操作(或操作数在只有一 些位元需要转化)时,需要允许寄存器, 你可能二者选一,理论上把函数一分我二,一个读操作和一个写操作。在指令执行时他们之间的关系通过他们需要在相同的位置表达出来。你可以对两个操作都使用 C表达,或不同的表达。例如,这是我们写的指令作为读操作源并且foo 作为读写的目的地址:
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));
对操作数1的约束“0”要与操作数0占用相同的存储地址。在约束中的数必须在输入中被允许并且它必须提及输出操作数。
只有在约束中的一个操作数目可以保证一个操作数与另一个在相同的位置。事实上foo两个操作数的值不能保证他们在汇编代码中相同的位置。以下语句是不可靠的:
        asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar));
不同最佳化或重载将引起操作数0和1在不同的寄存器中;GCC不知道不这样做的原因。例如,编译器可能在寄存器中找到foo值的一个副本( 然后到自己地址复制它)。当然因为对操作数1寄存器在汇编代码中不被提及,结果将不会工作,但是GCC不会提示错误。
如GCC 3.1版,可以为了匹配约束写[name]来代替操作数。例如:
          asm ("cmoveq %1,%2,%[result]"
          : [result] "=r"(result)
          : "r" (test), "r"(new), "[result]"(old));
一些指令破坏硬件寄存器。为了描述这个情况,在输入运算元之后写第三个冒号,在硬件寄存器的名字后面(如字符串所给。这是一个为VAX 例子:
asm volatile ("movc3 %0,%1,%2"
                   : /* no outputs */
                   : "g" (from), "g" (to), "g" (count)
                   : "r0", "r1", "r2", "r3", "r4", "r5");
你 可能在一定程度上对输入或输出操作数不会写描述。例如,如果没有在目录中提及寄存器你可能不会描述操作数。在没有指定输出操作时没有方法来指定输入操作。 注意如果你叙述的所有输出运算元为这个给目的 ( 并且因此不用), 然后将需要为 asm描述构造,同样地在下面描述, 阻止GCC删除asm。如果你在汇编码中提及一个特殊的硬件寄存器,你可能需要在第三个冒号之后向编译器指定寄存器记录器的数值。在汇编程序中,寄存器的 名字从"%"开始;在汇编代码中创建"%",必须在输入中写"%%".
如果你的汇编指令能够改变条件码寄存器,则把cc加入寄存器列表。 在一些机器上GCC 表现为一个特殊的硬件寄存器条件码; cc为寄存器命名。在其他的机器上,条件码不同方式地被处理,并且指定cc此时是没有作用的。但无论对于什么机器它是正确的。 如果你的汇编程序以不可预知方式修改存储器,把存储器加入寄存器列表。这将会导致GCC不保存在汇编指令对面的寄存器中被贮藏的存储器的值。如果改变的存 储器不在asm 的输入或输出中列出,你可能想要增加可变关键字,如存储器忽略asm的边际效应一样。
你可以在一个单一asm中将多重寄存器指令 集合起来, 并使用系统中汇编代码的正常字符将其分隔开。联合大多数都回在新的一行开始工作,加一个"tab "将其移到指令的位置( 写作: /n/ t). 有时可以使用分号,如果汇编程序允许分号作为换行字符。注意有些汇编程序使用分号作为注释的开始.输入操作要保证不使用寄存器,还有输出操作数的地址,所 以你可以不限次数的读写寄存器.这是一个多重指令的例子;它假定子程序_foo接受寄存器9 和10 的独立变量:
asm ("movl %0,r9/n/tmovl %1,r10/n/tcall _foo"
          : /* no outputs */
          : "g" (from), "g" (to)
          : "r9", "r10");
除非一个输出运算元包含&强制约束修正,GCC可能在同一个无关的输入操作数记录器中分配它,假设在输出产生之前输入被销毁。如果汇编码由多于一条指令组成这种假定可能是错误的.在这种情况时,对每个不可能重叠的输出操作数使用,见修正量。
如果你想测试由编译器产生的条件码, 你必须在asm构造中包含一个分支和一个标志, 如下:

     asm ("clr %0/n/tfrob %1/n/tbeq 0f/n/tmov #1,%0/n0:"
          : "g" (result)
          : "g" (input));
    
这假定了你的汇编程序支持本地标志,就如GNU汇编程序和大多数的 Unix程序一样。
说到标志, 从一个asm跳转到另外一个是不被支持的。编译器的优化不识别这些跳转,所以在优化的时候不会考虑这些跳转。
通常使用这些asm 指令最方便的方法是象函数一样压缩它们,如下:
       #define sin(x)       /
     ({ double __value, __arg = (x);   /
        asm ("fsinx %1,%0": "=f" (__value): "f" (__arg));  /
        __value; })
这 里变量__arg用来保证指令操作一个double值,并且只承认可以自动转换的那些独立变量x。另一种确定指令操作正确数据类型的方法是在asm中使用 一个计算。它不同与使用变量__arg的地方是它转换更多的不同数据类型。例如,如果需要的是int,int函数中的独立变量会无条件地承认一个指针,除 非调用者明确地指定它否则在分配一个int变量名的时候会出现警告。
如果asm有输出运算, GCC承认指令除了改变输出运算元之外没有其他的操作。这并不是说有边际效应的指令就不能使用了,但是必须小心的使用,因为如果在不用是输出运算时, 或把他们移出循环时,或他们有一个sub表达用一个代替了两者时,编译器可能消除他们。同时, 如果你的指令在变量上没有边际效应也不出现转换,如果巧合在寄存器中发现变量,那么它的初始值可能稍后被重新使用。
你可以通过在asm后写关键字来阻止asm指令被删除、移动、或联合,例如:
    #define get_and_set_priority(new)              /
     ({ int __old;                                  /
        asm volatile ("get_and_set_priority %0, %1" /
                      : "=g" (__old) : "g" (new));  /
        __old; })
   如果你写了一条没有输出的asm指令,GCC将会识别有其他作用并且不会在循环外删除或移动它。关键字指出指令有重要的作用。如果它是可得到的, GCC将不删除可变的asm ( 如果GCC能确定程序将不会执行到指令的位置,指令仍然会被删除)。除此之外,GCC将不会在可变asm指令中重置指令。例如:
   *(volatile int *)addr = foo;
     asm volatile ("eieio" : : );
假定地址中包含了寄存器列表中存储器的地址。 addr 包含存储器图表装置记录器的位址。PC的eieio指令(按顺序地执行I/O操作)将指定CPU确保在其他的I/O之前将它存储到设备寄存器中。
注 意,如果对编译器无意义的话可变asm指令也可能移动,如跳转指令。你不能认为可变asm指令的顺序性会保持地非常连续。 如果你想要连续的输出,可以使用单一asm 。 同时, GCC将会为asm指令执行一些优化; 当GCC遇到一个可变asm指令方式如一些其他的编译器不会忘记每个操作。
一条没有没有任何操作的指令("旧风格"的asm)将会同样地被处理 成可变asm指令。 这是寻找被汇编指令留下的条件码的一个很自然的想法。然而,当我们试图实现的时候,发现没有任何方法能确保工作的可靠性。问题在于输出可能需要重载,从而 导致需要额外的存储指令。大多数的机器上,测试它的时间之前,这些指令会改变条件码。问题的原因并不是普通的" 测试 "和" 比较 "指令,因为他们没有任何的输出操作。如上述的原因一样,不可能将先前指令留下的条件码传给汇编指令。如果你正在写一个头文件,应该包括ISO C 程序,使用__asm__ 代替asm。见预备关键字。
i386 浮点指针asm操作
有asm_operands中使用堆栈有一些规则。这些规则只适用于如堆栈一样的运算元为普通型的:
在asm_operands中给予一组输出规则,有必要知道哪些是被asm退栈的,还有哪写是被gcc明确退栈的。被asm捕获的输入规则必需被明确的识别,除非它是强制与输出操作匹配的。
对于任何被asm退栈输入信息,必需知道该如何调整堆栈为退栈补偿。如果任何的非退栈输入比退栈信息更靠近栈顶,将不会知道堆栈如何就象不清楚堆栈是如何检测的一样。所有的非退栈输入要比退栈信息更靠近栈顶。
在insn输入可能被消除,可能使用输入为输出重载,考虑下面的例子:
          asm ("foo" : "=t" (a) : "f" (b));
    程序中输入B不被出栈,并且asm将结果如栈,也就是说栈比以前更深了一位。但是如果输入B在insn中消除的话,重载可能考虑为输入和输出使用相同的信息。
如果任何的输入操作使用f强制约束, 那么普所有输出强制约束必须使用&earlyclobber。
语句应该如下:
          asm ("foo" : "=&t" (a) : "f" (b));
一些运算元需要在堆栈中特殊位置中。所有的输出都属于这个范畴--没有其他方法可以知道输出的出现除非用户强制地指定它。
输出运算必须明确地指出哪一个输出在asm之后出现。 “=f”不承认: 操作数必须选择一个类。
输出运算不可能在现有的堆栈之间被嵌套。因为没有387运算码使用读/写操作,在asm_operands之前所有的输出运算都被消除,并且被asm_operands压入堆栈,放在栈顶。
输出运算必须在栈顶开始,它不可能跳过reg。
一些asm声明可能因内部的计算需要额外的堆栈空间。这可以通过与输入和输出武官的寄存器来保证。
以下为两个合理的写法:
这个有一个输入,被出栈,产生两个输出:
     asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
   这个有两个输入,被fyl2xp1操作码出栈,并且将他们与一个输入做交换。用户必须为reg-stack.c 编码st(1)从而知道将两个输入出栈:
     asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)”);

 

原帖: http://www.sf.org.cn/Article/base/200604/17413.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值