一、什么是内联汇编
在标准的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;
}