汇编学习——使用内联汇编

一、什么是内联汇编

     在标准的C或者C++程序中,在文本源代码文件中按照C或者C++语法输入代码。然后使用编译器

把源代码编译为汇编代码。这个步骤之后,汇编语言代码和所有必须的库连接在一起生成可执行程

序。

     下面是一个简单 C语言程序:

#include <stdio.h>

float circumf( int a )
{
  return 2 * a * 3.14159;
}

float area( int a )
{
  return a * a * 3.14159;
}

int main()
{
  int x = 10;
  printf( "Radius: %d\n", x );
  printf( "Circumference: %f\n", circumf( x ) );
  printf( "Area: %f\n", area( x ) );
  return 0;
}

       使用gcc的-S选项进行编译:

gcc -S cfunctest.c
	.file	"cfunctest.c"
	.text
	.globl	circumf
	.type	circumf, @function
circumf:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$8, %esp
	movl	8(%ebp), %eax
	addl	%eax, %eax
	movl	%eax, -8(%ebp)
	fildl	-8(%ebp)
	fldl	.LC0
	fmulp	%st, %st(1)
	fstps	-4(%ebp)
	flds	-4(%ebp)
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	circumf, .-circumf
	.globl	area
	.type	area, @function
area:
.LFB1:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$8, %esp
	movl	8(%ebp), %eax
	imull	8(%ebp), %eax
	movl	%eax, -8(%ebp)
	fildl	-8(%ebp)
	fldl	.LC0
	fmulp	%st, %st(1)
	fstps	-4(%ebp)
	flds	-4(%ebp)
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	area, .-area
	.section	.rodata
.LC2:
	.string	"Radius: %d\n"
.LC3:
	.string	"Circumference: %f\n"
.LC4:
	.string	"Area: %f\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB2:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$32, %esp
	movl	$10, 28(%esp)
	movl	28(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC2, (%esp)
	call	printf
	movl	28(%esp), %eax
	movl	%eax, (%esp)
	call	circumf
	fstpl	4(%esp)
	movl	$.LC3, (%esp)
	call	printf
	movl	28(%esp), %eax
	movl	%eax, (%esp)
	call	area
	fstpl	4(%esp)
	movl	$.LC4, (%esp)
	call	printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE2:
	.size	main, .-main
	.section	.rodata
	.align 8
.LC0:
	.long	-266631570
	.long	1074340345
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
	.section	.note.GNU-stack,"",@progbits

      如果想直接控制生成什么汇编语言代码去实现函数,可以有以下3种选择:

  • 从头开始编写汇编语言代码来实现函数,然后从C程序调用它
  • 使用-S选项创建C代码的汇编语言版本,在必要的情况下修改汇编语言代码,然后连接汇编语言代码从而生成可执行文件。
  • 在原始的C代码内创建函数的汇编语言版本,然后使用标准C编译进行编译。

      第三种选项恰恰是内联汇编语言程序设计的工作方式。这种方法可以在C或C++程序源代码本

身之内创建汇编语言函数,不必连接额外的库或者程序。这种方法对于最终程序在汇编语言级别

如何实现特定的函数,给予程序员更多的控制权。

二、基本的内联汇编代码

       创建内联汇编代码和创建汇编函数没有太大区别,除了这是在C或者C++程序内完成的之外。

     1、asm格式

      GNU的C编译器使用asm关键字指出使用汇编语言编写的源代码段落。asm段的基本格式如

下:

asm( "assembly code" )

       包含在括号中的汇编代码必须按照特定的格式:

  • 指令必须扩在引号中
  • 如果包含的指令超过一条,那么必须使用新行字符分隔汇编语言代码的每一行。通常,还包含制表符帮助缩进汇编语言代码,使代码更容易阅读。

       需要第二个规则是因为编译器逐字取得asm段中的汇编代码,并且把它们放在为程序生成的

汇编代码中。每条汇编语言指令都必须在单独的一行中——因此需要包含新行字符。

        下面是基本内联汇编段的一个例子:

asm( "movl $1, %eax\n\tmovl $0, %ebx\n\tint $0x80" );

         这种格式显得很凌乱。可以如下做:

asm( "movl $1, %eax\n\t"
         "movl $0, %ebx\n\t"
         "int $0x80" );

          这种格式就容易阅读多了,在调试应用程序时会轻松得多。

          下面例子演示asm段在实际程序中的样子:

#include <stdio.h>

int main()
{
  int a = 10;
  int b = 20;
  int result;
  result = a * b;
  asm( "nop" );
  printf( "The result is %d\n", result );
  return 0;
}
	.file	"asmtest.c"
	.section	.rodata
.LC0:
	.string	"The result is %d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$32, %esp
	movl	$10, 20(%esp)
	movl	$20, 24(%esp)
	movl	20(%esp), %eax
	imull	24(%esp), %eax
	movl	%eax, 28(%esp)
#APP
# 9 "asmtest.c" 1
	nop
# 0 "" 2
#NO_APP
	movl	28(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC0, (%esp)
	call	printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
	.section	.note.GNU-stack,"",@progbits

       生成代码使用一般的C样式函数开头并且使用LEAVE指令实现标准的结尾。在开头和结尾代

码之内是C源代码生成的代码,其中有一个由#APP和#NO_APP符号标识的段落。这个段落包含

asm段指定的内联汇编代码。注意如何使用新行和制表符字符指定代码的布局。

     2、使用全局C变量

      仅仅实现汇编语言代码本身并不能完成很多任务。为了完成任何实际的工作,必须有把数据

传递进和传递出内联汇编语言函数的途径。

       基本的内联汇编代码可以利用应用程序中定义的全局C变量。只有全局定义的变量才能在基

本汇编代码内使用。通过C程序中使用的相同名称引用这种变量。

#include <stdio.h>

int a = 10;
int b = 20;
int result;

int main()
{

  asm( "pusha\n\t"
       "movl a, %eax\n\t"
       "movl b, %ebx\n\t"
       "imull %ebx, %eax\n\t"
       "movl %eax, result\n\t"
       "popa" );

  printf( "the answer is %d\n", result );

  return 0;
}

     编译器生成的汇编代码是这样的:

	.file	"globaltest.c"
	.globl	a
	.data
	.align 4
	.type	a, @object
	.size	a, 4
a:
	.long	10
	.globl	b
	.align 4
	.type	b, @object
	.size	b, 4
b:
	.long	20
	.comm	result,4,4
	.section	.rodata
.LC0:
	.string	"the answer is %d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
#APP
# 10 "globaltest.c" 1
	pusha 
	movl a, %eax
	movl b, %ebx
	imull %ebx, %eax
	movl %eax, result
	popa
# 0 "" 2
#NO_APP
	movl	result, %eax
	movl	%eax, 4(%esp)
	movl	$.LC0, (%esp)
	call	printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
	.section	.note.GNU-stack,"",@progbits

       注意在汇编语言代码开头的PUSHA指令,以及结尾的POPA指令。在进入代码之前存储寄存

器的初始值,然后在完成代码之后恢复它们,这一点很重要。在编译后的C源代码中,编译器很

有可能会使用这些寄存器存放其他值。如果在asm段中修改它们,将发生不可预测的后果。

     3、使用volatile修饰符

     在一般C或者C++应用程序中,编译器也许会试图优化生成的汇编代码以提高性能。通常这是

用过这样的方式完成的:消除不使用的函数,在不同时使用的值之间共享寄存器,以及重新编排

代码以便实现更好的程序流程。

      对于内联汇编函数来说,有时候优化并不是好事情。编译器也可能查看内联代码并且试图优

化它,这可能产生不希望的后果。

       如果希望编译器不处理手动编写的内联汇编函数,可以明确地这么说明!可以把volatile修饰

符放在asm语句中表示不希望优化这个代码段。使用volatile修饰符的asm语句的格式如下:

asm volatile ( "assembly code" );

     4、使用替换的关键字

     ANSI C规范把关键字asm用于其他用途,不能将它用于内联汇编语句。如果使用ANSI C约定

编写代码,你必须使用关键字__asm__替换一般的关键字asm。     

      语句中的汇编代码段不必改动,和使用asm关键字一样,就像下面的例子:

  __asm__( "pusha\n\t"
       "movl a, %eax\n\t"
       "movl b, %ebx\n\t"
       "imull %ebx, %eax\n\t"
       "movl %eax, result\n\t"
       "popa" );

       关键字__asm__也可以使用修饰符__volatile__进行修饰。

三、扩展asm

        基本的asm格式提供创建代码的简单方式,但是有其局限性。首先,所有输入值

和输出值必须使用C程序的全局变量。其次,必须极为注意在内联汇编代码中不去改

变任何寄存器的值。         

         GNU编译器提供asm段的扩展格式来帮助解决这些问题。

        1、扩展asm格式

         asm扩展版本格式如下:

asm ( "assembly code" : output locations : input operands : changed registers );

         这种格式由4个部分构成,使用冒号分隔:

  •   汇编代码:使用和基本asm格式相同的语法的内联汇编代码
  •   输出位置:包含内联汇编代码的输出值的寄存器和内存位置的列表
  •   输入操作数:包含内联汇编代码的输入值的寄存器和内存位置的列表
  •   改动的寄存器:内联代码改变的任何其他寄存器的列表

         在扩展asm格式中,并不是所有这些部分都必须出现。如果汇编代码不生成输出值,这个部

分就必须为空,但是必须使用两个冒号把汇编代码和输入操作数隔开。如果内联汇编代码不改动

寄存器值,那么可以忽略最后的冒号。

        2、指定输入值和输出值

        在基本asm格式中,在汇编代码中通过C全局变量名称整合输入值和输出值。使用扩展格式

时的方法稍有不同。

        在扩展格式中,可以从寄存器和内存位置给输入值和输出值赋值。输入值和输出值列表的格

式是:

"constraint" (variable)

/*其中variable是程序中声明的C变量。在扩展asm格式中,局部和全局变量都可以使用。
  constraint定义把变量存放到哪里(对于输入值)或者从哪里传送变量(对于输出值)。
  使用它定义把变量存放在寄存器中还是内存位置中*/

        约束是单一字符的代码。约束代码如下:

         除了这些约束之外,输出值还包含一个约束修饰符。它指示编译器如何处理输出值。

       例子如下:

asm ( "assembly code" : "=a"( result ) : "d" ( data1 ), "c" ( data2 ) );

      把C变量data1存放在EDX寄存器中,把data2存放到ECX寄存器中。内联汇编代码的结果将

存放在EAX寄存器中,然后传递给变量result。

        3、使用寄存器

        如果输入值和输出值变量被赋值给寄存器,那么在内联汇编代码中几乎可以像平常一样使用

寄存器。“几乎”一词说明有个特殊情况需要处理。

        在扩展asm格式中,为了唉汇编代码中引用寄存器,必须使用两个百分号符号,而不是一

个。

       下面程序演示了在扩展asm格式中使用寄存器:

#include <stdio.h>

int main()
{
  int data1 = 10;
  int data2 = 20;
  int result;
  
  __asm__ ( "imull %%edx, %%ecx\n\t"
            "movl %%ecx, %%eax"
            : "=a" ( result )
            : "d" (data1), "c" (data2) );
  printf( "The result is %d\n", result );
  
  return 0;
}
	.file	"regtest1.c"
	.section	.rodata
.LC0:
	.string	"The result is %d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$32, %esp
	movl	$10, 20(%esp) 
	movl	$20, 24(%esp)
	movl	20(%esp), %eax
	movl	24(%esp), %ecx
	movl	%eax, %edx
#APP
# 9 "regtest1.c" 1
	imull %edx, %ecx
	movl %ecx, %eax
# 0 "" 2
#NO_APP
	movl	%eax, 28(%esp)
	movl	28(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC0, (%esp)
	call	printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
	.section	.note.GNU-stack,"",@progbits

       编译器把data1和data2的值传送到为C变量保存的堆栈空间。然后按照内联汇编代码的要

求,把这些值加载到EDX和ECX寄存器中。然后把EAX寄存器中生成的结果输出传送到堆栈中的

变量位置result。

       不一定总要在内联汇编段中指定输出值。一些汇编指令已经假设输入值包含输出值。

       MOVS指令在输入值内包含输出位置。

       

#include <stdio.h>

int main()
{
  char input[ 30 ] = { "This is a test message.\n" };
  char output[ 30 ];
  int length = 25;

  __asm__ __volatile__ ( "cld\n\t"
                         "rep movsb"
                         :
                         : "S" ( input ), "D" ( output ), "c" ( length ) );
  printf( "%s", output );
  
  return 0;
}

        4、使用占位符

       前面处理输入输出值对于很多的情况下就会显得繁琐。

       为了帮助解决这个问题,扩展asm格式提供了占位符(placeholder),可以在内联汇编代码

中使用它引用输入值和输出值。这样可以在对于编译器方便的任何寄存器或者内存位置中声明输

入和输出值。

       占位符是前面加上百分号符号的数字。按照内联汇编代码中列出的每个输入值和输出值在列

表中的位置,每个值被赋予一个从零开始的数字。然后就可以在汇编代码中使用占位符表示值。

        例如,下面的内联代码:

asm ( "assembly code"
      : "=r" ( result )
      : "r" ( data1 ), "r" ( data2 ) );

        将生成如下的占位符:

  •   %0 将表示包含变量值result的寄存器
  •   %1 将表示包含变量值data1的寄存器
  •   %2 将表示包含变量值data2的寄存器

       注意,占位符提供在内联汇编代码中利用寄存器和内存位置的方法。汇编代码中使用占位符

只作为原始的数据类型:

imull %1, %2
movl %2, %0

      下面演示占位符:

#include <stdio.h>

int main()
{
  int data1 = 10;
  int data2 = 20;
  int result;
  
  __asm__ ( "imull %1, %2\n\t"
            "movl %2, %0"
            : "=a" ( result )
            : "d" (data1), "c" (data2) );
  printf( "The result is %d\n", result );
  
  return 0;
}

        5、引用占位符

   有时候使用相同的变量作为输入输出值是有好处的。要这样做,必须在扩展asm段中区别定

义输入值和输出值。

         如果内联汇编代码中的输入值和输出值共享程序中相同的C变量,可以指定使用占位符作为

约束值。这样便于减少代码中需要寄存器的数量。

         可以编写下面代码:

asm ( "imull %1, %0"
      : "=r" ( data2 )
      : "r" ( data1 ), "0" ( data2 ) );

         0标记通知编译器使用第一个命名的寄存器存放输出值data2。第二行代码定义第一个命名

寄存器,它为输入变量data2分配一个寄存器。这样确保将使用相同的寄存器保存输入值和输出

值。内联代码完成时,结果将被存到值data2中。    

#include <stdio.h>

int main()
{
  int data1 = 10;
  int data2 = 20;
  
  __asm__ ( "imull %1, $0"
            : "=r" ( data2 )
            : "r" ( data1 ), "0" ( data2 ) );
  printf( "The result is %d\n", data2 );
  
  return 0;
}

        6、替换占位符

       替换的名称在声明输入值和输出值的段中定义。格式如下:

%[name] "constrint" (variable)

        定义的值name称为内联汇编代码中变量的新的占位符标志符,如下例子:

asm ( "imul %[value1], %[value2]"
      : [value2] "=r" ( data2 )
      : [value1] "r" ( data1 ), "0" ( data2 ) );

        使用替换的占位符名称的方式和普通的占位符相同。

#include <stdio.h>

int main()
{
  int data1 = 10;
  int data2 = 20;
  
  __asm__ ( "imull %[value1], $[value2]"
            : [value1] "=r" ( data2 )
            : [value2] "r" ( data1 ), "0" ( data2 ) );
  printf( "The result is %d\n", data2 );
  
  return 0;
}

        7、改动的寄存器列表

         改动寄存器列表的正确方法是,如果内联汇编代码使用了没有被初始地声明为输入值或者输

出值的任何其他寄存器,则要通知编译器,编译器必须直到这些寄存器,以便避免使用它们,如

下所示:          

#include <stdio.h>

int main()
{
  int data1 = 10;
  int result = 20;
  
  __asm__ ( "movl %1, %%eax\n\t"
            "addl %%eax, %0"
            : "=r" ( result )
            : "r" ( data1 ), "0" ( result )
            : "%eax" );
  printf( "The result is %d\n", result );
  
  return 0;
}

         上面程序中,内联汇编代码使用EAX寄存器作为存储数据值的中间位置。因为这个寄存器

没有被声明为输入值或输出值,所以必须在改动的寄存器列表中包含它。

           现在编译器直到EAX寄存器不可用,它会避免使用这个寄存器。

        8、使用内存位置

          也可以使用C变量的内存位置。对于要求使用寄存器的汇编指令,仍然必须使用寄存器,所

以也许不得不定义保存数据的中间寄存器。

#include <stdio.h>
  
int main()
{
  int dividend = 20;
  int divisor = 5;
  int result;

  __asm__ ( "divib %2\n\t"
            "movl %%eax, %0"
            : "=m" ( result )
            : "a" ( dividend ), "m" ( divisor ) );
  printf( "The result is %d\n", result );
  
  return 0;
}

         asm段把被除数加载到EAX寄存器中,DIV指令需要它。除数存放在内存位置中,作为输出

值。值被加载到内存位置中(堆栈中),被除数的值也被传送到EAX寄存器中,确定结果之后,

他被传送到堆栈中它的内存位置中,而不是寄存器。

        9、使用浮点值

        处理FPU寄存器堆栈的约束有3个:

  • f引用任何可用的浮点寄存器
  • t引用顶部的浮点寄存器
  • u引用第二个浮点寄存器

         从FPU获得输出值的时候,不能使用约束f,必须声明约束t或者u来指定输出值所在的FPU

寄存器,就像下面的样子:

asm ( "fsincos"
      : "=t" (cosine), "=u" ( sine )
      : "0" ( radian ) );

 

#include <stdio.h>

int main()
{
  float angle = 90;  
  float radian, cosine, sine;
  
  radian = angle / 180 * 3.14159;

  asm ( "fsincos"
        : "=t" (cosine), "=u" ( sine )
        : "0" ( radian ) );
  printf( "The cosine is %f, and the sine is %f\n", cosine, sine );

  return 0;
}

         上面例子中,因为编译器知道输出值位于前两个FPU寄存器中,所以它会弹出这些值,把

FPU堆栈恢复为以前的状态。如果在FPU堆栈中执行的任何操作没有被清除,就必须在改动的寄

存器列表中指定适当的FPU寄存器。如下所示:

#include <stdio.h>

int main()
{
  int radius = 10;  
  float area;

  __asm__ ( "fild %1\n\t"
            "fimul %1\n\t"
            "fldpi\n\t"
            "fmul %%st(1), %%st(0)"
            : "=t" ( area )
            : "m" ( radius )  
            : "%st(1)" );
  printf( "The result is %f\n", area );

  return 0;
}

        因为使用了ST(1)寄存器,但是没有把它赋值为输出值,所以必须在改动的寄存器列表中列

出它,以便编译器直到在处理完成之后情况它。

        10、处理跳转

          内联汇编语言代码也可以包含定义其中位置的标签。可以实现一般的汇编条件分治和无条

件分支,跳转到定义的标签。

#include <stdio.h>

int main()
{
  int a = 10;
  int b = 20;
  int result;

  __asm__ ( "cmp %1, %2\n\t"
            "jge greater\n\t"
            "jmp end\n"
            "greater:\n\t"
            "movl %2, %0\n"
            "end:"
            : "=r" ( result )
            : "r" ( a ), "r" ( b ) );
  printf( "The larger value is %d\n", result );
  
  return 0;
}

      在内联汇编代码中使用标签时有两个限制。第一个限制是只能跳转到相同的asm段内的标

签。不能从一个asm段跳转到另一个asm段中的标签。

       第二个限制更加复杂一些。不能在不同asm段中使用相同的标签,否则会因为标签的重复使

用而导致错误消息。还有,如果试图整合使用C关键字(比如函数名称或者全局变量)的标签,

也会导致错误。

        这个问题有两个解决办法:最简单的解决方案是在不同的asm段中使用不同的标签。或者使

用局部标签。

        条件分支和无条件分支都允许指定一个数字加上方向标志作为标签。方向标志指出处理器应

该向哪个方向查找数字型标签。第一个遇到的标签会被采用。

#include <stdio.h>

int main()
{
  int a = 10;
  int b = 20;
  int result;

  __asm__ ( "cmp %1, %2\n\t"
            "jge 0f\n\t"
            "movl %1, %0\n\t"
            "jmp 1f\n"
            "0:\n\t"
            "movl %2, %0\n"
            "1:"
            : "=r" ( result )
            : "r" ( a ), "r" ( b ) );
  printf( "The larger value is %d\n", result );
  
  return 0;
}

         标签被替换为0:和1:。JGE和JMP指令使用修饰符f指出从跳转指令向前查找标签。要想

向后移动,必须使用修饰符b。

四、使用内联汇编代码

      1、什么是宏

       在C和C++程序中,宏被用于定义从常量值到复杂函数的任何内容。使用#define

语句定义宏,格式如下:

#define NAME expression

        按照约定,总是使用大写字母定义宏名字NAME(这样确保不会和C库函数冲突)。值

expression可以是常量的数字型或者字符串值。

         可以把宏定义为数字型的值,如下所示:

#define MAX_VALUE 1024

          宏不能被改变,整个程序中保持不变。

      2、C宏函数

       宏函数的格式如下:

#define NAME( input values, output value ) ( function )

       输入值是用逗号分隔的用作函数输入的变量的列表。函数定义如何处理输入值来生成输出

值。为了提高可读性,可以使用继续字符(反斜线)分割函数,如下所示:

#define SUM( a, b, result ) \ 
        ( ( result ) = ( a ) + ( b ) )
#include <stdio.h>

#define SUM( a, b, result ) \
        ( ( result ) = ( a ) + ( b ) )

int main()
{
  int data1 = 5, data2 = 10;
  int result;
  float fdata1 = 5.0, fdata2 = 10.0;
  float fresult;

  SUM( data1, data2, result );
  printf( "The result is %d\n", result );
  SUM( 1, 1, result );
  printf( "The result is %d\n", result );
  SUM( fdata1, fdata2, fresult );
  printf( "The floating result is %f\n", fresult );
  SUM( fdata1, fdata2, result );
  printf( "The mixed result is %d\n", result );
  
  return 0;
}

      3、 创建内联汇编宏函数

      内联汇编代码必须使用扩展asm格式,以便可以定义正确的输入值和输出值。因

为程序中可以多次使用宏函数,所以还应该在汇编代码需要的任何分治语句中使用数

字型标签。

       例子如下:

#define GREATER( a, b, result ) ( { \
  __asm__ ( "cmp %1, %2\n\t" \
            "jge 0f\n\t" \
            "movl %1, %0\n\t" \
            "jmp 1f\n" \
            "0:\n\t" \
            "movl %2, %0\n" \
            "1:" \
            : "=r" ( result ) \
            : "r" ( a ), "r" ( b ) ); } )

     注意asm语句必须括在一对花括号中,以便指出语句的开头和结尾。如果没有花括号,每次在

C代码中使用宏时,编译器都会生成错误代码。

#include <stdio.h>

#define GREATER( a, b, result ) ( { \
  __asm__ ( "cmp %1, %2\n\t" \
            "jge 0f\n\t" \
            "movl %1, %0\n\t" \
            "jmp 1f\n" \
            "0:\n\t" \
            "movl %2, %0\n" \
            "1:" \
            : "=r" ( result ) \
            : "r" ( a ), "r" ( b ) ); } )

int main()
{
  int data1 = 5;
  int data2 = 10;
  int result;

  GREATER( data1, data2, result );
  printf( "a = %d, b = %d, result: %d\n", data1, data2, result );
  data1 = 30;
  GREATER( data1, data2, result );
  printf( "a = %d, b = %d, result: %d\n", data1, data2, result );
  
  return 0;
}

 

转载于:https://my.oschina.net/u/2537915/blog/699302

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值