C语⾔ASM汇编内嵌语法【转】
转⾃:
GCC ⽀持在C/C++代码中嵌⼊汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是⼀个⾮常有⽤的功能,有利于我们将⼀些C/C++语法⽆法表达的指令直
接潜⼊C/C++代码中,另外也允许我们直接写 C/C++代码中使⽤汇编编写简洁⾼效的代码。
1.基本内联汇编
GCC中基本的内联汇编⾮常易懂,我们先来看两个简单的例⼦:
__asm__("movl %esp,%eax"); // 看起来很熟悉吧!
或者是
__asm__("
movl 1,0x80
");
或
__asm__(
"movl 1,0x80" \
);
基本内联汇编的格式是
__asm__ __volatile__("Instruction List");
1、__asm__
__asm__是GCC关键字asm的宏定义:
#define __asm__ asm
__asm__或asm⽤来声明⼀个内联汇编表达式,所以任何⼀个内联汇编表达式都是以它开头的,是必不可少的。
2、Instruction List
Instruction List是汇编指令序列。它可以是空的,⽐如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并⾮所
有Instruction List为空的内联汇编表达式都是没有意义的,⽐如:__asm__ ("":::"memory"); 就⾮常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因
素考虑进去。
我们看⼀看下⾯这个例⼦:
cat example1.c int main(int __argc, char* __argv[]) { int* __p = (int*)__argc; (*__p) = 9999; //__asm__("":::"memory"); if((*__p) == 9999) return 5; return (*__p); } 在这段代码中,那条内联汇编是被注释掉的。在这条内gcc -O -S example1.c
选项-O表⽰优化编译,我们还可以指定优化等级,⽐如-O2表⽰优化等级为2;选项-S表⽰将C/C++源⽂件编译为汇编⽂件,⽂件名和C/C++⽂件⼀样,只不过扩展名由.c变为.s。
我们来查看⼀下被放在example1.s中的编译结果,我们这⾥仅仅列出了使⽤gcc 2.96在redhat 7.3上编译后的相关函数部分汇编代码。为了保持清晰性,⽆关的其它代码未被列
出。
catexample1.smain:pushl9999, (%eax) # (*__p) = 9999
movl 5, gcc -O -S example1.c
catexample1.smain:pushl9999, (%eax) # (*__p) = 9999
#APP
# __asm__("":::"memory")
#NO_APP
cmpl 9999,(5, %eax # true, return 5
jmp .L2
.p2align 2
.L3:
movl (%eax), %eax
.
L2:
popl %ebp
ret
由于内联汇编语句__asm__("":::"memory")向GCC声明,在此内联汇编语句出现的位置内存内容可能了改变,所以GCC在编译时就不能像刚才那样处理。这次,GCC⽼⽼实实的
将if语句⽣成了汇编代码。
可能有⼈会质疑:为什么要使⽤__asm__("":::"memory")向GCC声明内存发⽣了变化?明明“Instruction List”是空的,没有任何对内存的操作,这样做只会增加GCC⽣成汇编代码
的数量。
确实,那条内联汇编语句没有对内存作任何操作,事实上它确实什么都没有做。但影响内存内容的不仅仅是你当前正在运⾏的程序。⽐如,如果你现在正在操作的内存是⼀块内
存映射,映射的内容是外围I/O设备寄存器。那么操作这块内存的就不仅仅是当前的程序,I/O设备也会去操作这块内存。既然两者都会去操作同⼀块内存,那么任何⼀⽅在任何
时候都不能对这块内存的内容想当然。所以当你使⽤⾼级语⾔C/C++写这类程序的时候,你必须让编译器也能够明⽩这⼀点,毕竟⾼级语⾔最终要被编译为汇编代码。
你可能已经注意到了,这次输出的汇编结果中,有两个符号:#APP和#NO_APP,GCC将内联汇编语句中"Instruction List"所列出的指令放在#APP和#NO_APP之间,由于
__asm__("":::"memory")中“Instruction List”为空,所以#APP和#NO_APP中间也没有任何内容。但我们以后的例⼦会更加清楚的表现这⼀点。
关于为什么内联汇编__asm__("":::"memory")是⼀条声明内存改变的语句,我们后⾯会详细讨论。
刚才我们花了⼤量的内容来讨论"Instruction List"为空是的情况,但在实际的编程中,"Instruction List"绝⼤多数情况下都不是空的。它可以有1条或任意多条汇编指令。
当在"Instruction List"中有多条指令的时候,你可以在⼀对引号中列出全部指令,也可以将⼀条或⼏条指令放在⼀对引号中,所有指令放在多对引号中。如果是前者,你可以将
每⼀条指令放在⼀⾏,如果要将多条指令放在⼀⾏,则必须⽤分号(;)或换⾏符(\n,⼤多数情况下\n后还要跟⼀个\t,其中\n是为了换⾏,\t是为了空出⼀个tab宽度的空格)
将它们分开。⽐如:
__asm__("movl %eax, %ebx
sti
popl %edi
subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti
popl %edi; subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti\n\t popl %edi
subl %ecx, %ebx");
都是合法的写法。如果你将指令放在多对引号中,则除了最后⼀对引号之外,前⾯的所有引号⾥的最后⼀条指令之后都要有⼀个分号(;)或(\n)或(\n\t)。⽐如:
__asm__("movl %eax, %ebx
sti\n"
"popl %edi;"
"subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti\n\t"
"popl %edi; subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti\n\t popl %edi\n"
"subl %ecx, %ebx");
__asm__("movl %eax, %ebx; sti\n\t popl %edi;" "subl %ecx, %ebx");
都是合法的。
上述原则可以归结为:
任意两个指令间要么被分号(;)分开,要么被放在两⾏;
放在两⾏的⽅法既可以从通过\n的⽅法来实现,也可以真正的放在两⾏;
可以使⽤1对或多对引号,每1对引号⾥可以放任⼀多条指令,所有的指令都要被放到引号中。
在基本内联汇编中,“Instruction List”的书写的格式和你直接在汇编⽂件中写⾮内联汇编没有什么不同,你可以在其中定义Label,定义对齐(.align n ),定义段(.section name )。例如:
__asm__(".align 2\n\t"
"movl %eax, %ebx\n\t"
"test %ebx, %ecx\n\t"
"jne error\n\t"
"sti\n\t"
"error: popl %edi\n\t"
"subl %ecx, %ebx");
上⾯例⼦的格式是Linux内联代码常⽤的格式,⾮常整齐。也建议⼤家都使⽤这种格式来写内联汇编代码。
3、__volatile__
__volatile__是GCC关键字volatile的宏定义:
#define __volatile__ volatile
__volatile__ 或volatile是可选的,你可以⽤它也可以不⽤它。如果你⽤了它,则是向GCC声明“不要动我所写的Instruction List,我需要原封不动的保留每⼀条指令”,否则当你使⽤了优化选项(-O)进⾏编译时,GCC将会根据⾃⼰的判断决定是否将这个内联汇编表达式中的指令优化掉。
那么GCC判断的原则是什么?我不知道(如果有哪位朋友清楚的话,请告诉我)。我试验了⼀下,发现⼀条内联汇编语句如果是基本内联汇编的话(即只有“Instruction List”,没有Input/Output/Clobber的内联汇编,我们后⾯将会讨论这⼀点),⽆论你是否使⽤__volatile__来修饰, GCC 2.96在优化编译时,都会原封不动的保留内联汇编中的“Instruction List”。但或许我的试验的例⼦并不充分,所以这⼀点并不能够得到保证。
为了保险起见,如果你不想让GCC的优化影响你的内联汇编代码,你最好在前⾯都加上__volatile__,⽽不要依赖于编译器的原则,因为即使你⾮常了解当前编译器的优化原则,你也⽆法保证这种原则将来不会发⽣变化。⽽__volatile__的含义却是恒定的。
2、带有C/C++表达式的内联汇编
GCC允许你通过C/C++表达式指定内联汇编中"Instrcuction List"中指令的输⼊和输出,你甚⾄可以不关⼼到底使⽤哪个寄存器被使⽤,完全靠GCC来安排和指定。这⼀点可以让程序员避免去考虑有限的寄存器的使⽤,也可以提⾼⽬标代码的效率。
我们先来看⼏个例⼦:
__asm__ (" " : : : "memory" ); // 前⾯提到的
__asm__ ("mov %%eax, %%ebx" : "=b"(rv) : "a"(foo) : "eax", "ebx");
__asm__ __volatile__("lidt %0": "=m" (idt_descr));
__asm__("subl %2,%0\n\t"
"sbbl %3,%1"
: "=a" (endlow), "=d" (endhigh)
: "g" (startlow), "g" (starthigh), "0" (endlow), "1" (endhigh));
怎么样,有点印象了吧,是不是也有点晕?没关系,下⾯讨论完之后你就不会再晕了。(当然,也有可能更晕^_^)。讨论开始——
带有C/C++表达式的内联汇编格式为:
__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
从中我们可以看出它和基本内联汇编的不同之处在于:它多了3个部分(Input,Output,Clobber/Modify)。在括号中的4个部分通过冒号(:)分开。
这4个部分都不是必须的,任何⼀个部分都可以为空,其规则为:
如果Clobber/Modify为空,则其前⾯的冒号(:)必须省略。⽐如__asm__("mov %%eax, %%ebx" : "=b"(foo) : "a"(inp) : )就是⾮法的写法;⽽__asm__("mov %%eax, %%ebx" : "=b" (foo) : "a"(inp) )则是正确的。
如果Instruction List为空,则Input,Output,Clobber/Modify可以不为空,也可以为空。⽐如__asm__ ( " " : : : "memory" );和__asm__(" " : : );都是合法的写法。
如果Output,Input,Clobber/Modify都为空,Output,Input之前的冒号(:)既可以省略,也可以不省略。
如果都省略,则此汇编退化为⼀个基本内联汇编,否则,仍然是⼀个带有C/C++表达式的内联汇编,此时"Instruction List"中的寄存器写法要遵守相关规定,⽐如寄存器前必须使⽤两个百分号(%%),⽽不是像基本汇编格式⼀样在寄存器前只使⽤⼀个百分号(%)。⽐如 __asm__( " mov %%eax, %%ebx" : : );__asm__( " mov %%eax, %%ebx" : )和__asm__( " mov %eax, %ebx" )都是正确的写法,⽽__asm__( " mov %eax,
%ebx" : : );__asm__( " mov %eax, %ebx" : )和__asm__( " mov %%eax, %%ebx" )都是错误的写法。
如果Input,Clobber/Modify为空,但Output不为空,Input前的冒号(:)既可以省略,也可以不省略。⽐如 __asm__( " mov %%eax, %%ebx" : "=b"(foo) : );__asm__( " mov
%%eax, %%ebx" : "=b"(foo) )都是正确的。
如果后⾯的部分不为空,⽽前⾯的部分为空,则前⾯的冒号(:)都必须保留,否则⽆法说明不为空的部分究竟是第⼏部分。⽐如, Clobber/Modify,Output为空,⽽Input不为空,则Clobber/Modify前的冒号必须省略(前⾯的规则),⽽Output 前的冒号必须为保留。如果Clobber/Modify不为空,⽽Input和Output都为空,则Input和Output前的冒号都必须保留。⽐如 __asm__( " mov %%eax, %%ebx" : : "a"(foo) )和__asm__( " mov %%eax, %%ebx" : : : "ebx" )。
从上⾯的规则可以看到另外⼀个事实,区分⼀个内联汇编是基本格式的还是带有C/C++表达式格式的,其规则在于在"Instruction List"后是否有冒号(:)的存在,如果没有则是基本格式的,否则,则是带有C/C++表达式格式的。
两种格式对寄存器语法的要求不同:基本格式要求寄存器前只能使⽤⼀个百分号(%),这⼀点和⾮内联汇编相同;⽽带有C/C++表达式格式则要求寄存器前必须使⽤两个百分号(%%),其原因我们会在后⾯讨论。
1. Output
Output⽤来指定当前内联汇编语句的输出。我们看⼀看这个例⼦:
__asm__("movl %%cr0, %0": "=a" (cr0));
这个内联汇编语句的输出部分为"=r"(cr0),它是⼀个“操作表达式”,指定了⼀个输出操作。我们可以很清楚得看到这个输出操作由两部分组成:括号括住的部分(cr0)和引号引住的部分"=a"。这两部分都是每⼀个输出操作必不可少的。括号括住的部分是⼀个C/C++表达式,⽤来保存内联汇编的⼀个输出值,其操作就等于C/C++的相等赋值cr0 =
output_value,因此,括号中的输出表达式只能是C/C++的左值表达式,也就是说它只能是⼀个可以合
法的放在C/C++赋值操作中等号(=) 左边的表达式。那么右值output_value 从何⽽来呢?
答案是引号中的内容,被称作“操作约束”(Operation Constraint),在这个例⼦中操作约束为"=a",它包含两个约束:等号(=)和字母a,其中等号(=)说明括号中左值表达式cr0是⼀个 Write-Only的,只能够被作为当前内联汇编的输⼊,⽽不能作为输⼊。⽽字母a是寄存器EAX / AX / AL的简写,说明cr0的值要从eax寄存器中获取,也就是说cr0 = eax,最终这⼀点被转化成汇编指令就是movl %eax, address_of_cr0。现在你应该清楚了吧,操作约束中会给出:到底从哪个寄存器传递值给cr0。
另外,需要特别说明的是,很多⽂档都声明,所有输出操作的操作约束必须包含⼀个等号(=),但GCC的⽂档中却很清楚的声明,并⾮如此。因为等号(=)约束说明当前的表达式是⼀个 Write-Only的,但另外还有⼀个符号——加号(+)⽤来说明当前表达式是⼀个Read-Write的,如果⼀个操作约束中没有给出这两个符号中的任何⼀个,则说明当前表达式是Read-Only的。因为对于输出操作来说,肯定是必须是可写的,⽽等号(=)和加号(+)都表⽰可写,只不过加号(+) 同时也表⽰是可读的。所以对于⼀个输出操作来说,其操作约束只需要有等号(=)或加号(+)中的任意⼀个就可以了。
⼆者的区别是:等号(=)表⽰当前操作表达式指定了⼀个纯粹的输出操作,⽽加号(+)则表⽰当前操作表达式不仅仅只是⼀个输出操作还是⼀个输⼊操作。但⽆论是等号(=)约束还是加号(+)约束所约束的操作表达式都只能放在Output域中,⽽不能被⽤在Input域中。
另外,有些⽂档声明:尽管GCC⽂档中提供了加号(+)约束,但在实际的编译中通不过;我不知道⽼版本会怎么样,我在GCC 2.96中对加号(+)约束的使⽤⾮常正常。
我们通过⼀个例⼦看⼀下,在⼀个输出操作中使⽤等号(=)约束和加号(+)约束的不同。
cat example2.c int main(int __argc, char* __argv[]) { int cr0 = 5; __asm__ __volatile__("movl %%cr0, %0":"=a" (cr0)); return 0; } gcc -S example2.c
catexample2.smain:pushl4, %esp
movl 5,−4(0, %eax
leave
ret
这个例⼦是使⽤等号(=)约束的情况,变量cr0被放在内存-4(%ebp)的位置,所以指令mov %eax, -4(%ebp)即表⽰将%eax的内容输出到变量cr0中。
下⾯是使⽤加号(+)约束的情况:
cat example3.c int main(int __argc, char* __argv[]) { int cr0 = 5; __asm__ __volatile__("movl %%cr0, %0" : "+a" (cr0)); return 0; } gcc -S example3.c
catexample3.smain:pushl4, %esp
movl 5,−4(0, %eax
leave
ret
从编译的结果可以看出,当使⽤加号(+)约束的时候,cr0不仅作为输出,还作为输⼊,所使⽤寄存器都是寄存器约束(字母a,表⽰使⽤eax寄存器)指定的。关于寄存器约束我们后⾯讨论。
在Output域中可以有多个输出操作表达式,多个操作表达式中间必须⽤逗号(,)分开。例如:
__asm__(
"movl %%eax, %0 \n\t"
"pushl %%ebx \n\t"
"popl %1 \n\t"
"movl %1, %2"
: "+a"(cr0), "=b"(cr1), "=c"(cr2));
2、Input
Input域的内容⽤来指定当前内联汇编语句的输⼊。我们看⼀看这个例⼦:
__asm__("movl %0, %%db7" : : "a" (cpu->db7));
例中Input域的内容为⼀个表达式"a"[cpu->db7),被称作“输⼊表达式”,⽤来表⽰⼀个对当前内联汇编的输⼊。
像输出表达式⼀样,⼀个输⼊表达式也分为两部分:带括号的部分(cpu->db7)和带引号的部分"a"。这两部分对于⼀个内联汇编输⼊表达式来说也是必不可少的。
括号中的表达式cpu->db7是⼀个C/C++语⾔的表达式,它不必是⼀个左值表达式,也就是说它不仅可以是放在C/C++赋值操作左边的表达式,还可以是放在C/C++赋值操作右边的表达式。所以它可以是⼀个
变量,⼀个数字,还可以是⼀个复杂的表达式(⽐如a+b/c*d)。⽐如上例可以改为: __asm__("movl %0, %%db7" : : "a" (foo)),__asm__("movl %0, %%db7" : : "a" (0x1000))或__asm__("movl %0, %%db7" : : "a" (va*vb/vc))。
引号号中的部分是约束部分,和输出表达式约束不同的是,它不允许指定加号(+)约束和等号(=)约束,也就是说它只能是默认的Read-Only的。约束中必须指定⼀个寄存器约束,例中的字母a表⽰当前输⼊变量cpu->db7要通过寄存器eax输⼊到当前内联汇编中。
我们看⼀个例⼦:
cat example4.c int main(int __argc, char* __argv[]) { int cr0 = 5; __asm__ __volatile__("movl %0, %%cr0"::"a" (cr0)); return 0; } gcc -S example4.c
catexample4.smain:pushl4, %esp
movl 5,−4(0, %eax
leave
ret
我们从编译出的汇编代码可以看到,在"Instruction List"之前,GCC按照我们的输⼊约束"a",将变量cr0的内容装⼊了eax寄存器。
3. Operation Constraint
每⼀个Input和Output表达式都必须指定⾃⼰的操作约束Operation Constraint,我们这⾥来讨论在80386平台上所可能使⽤的操作约束。
1、寄存器约束
当你当前的输⼊或输⼊需要借助⼀个寄存器时,你需要为其指定⼀个寄存器约束。你可以直接指定⼀个寄存器的名字,⽐如:
__asm__ __volatile__("movl %0, %%cr0"::"eax" (cr0));
也可以指定⼀个缩写,⽐如:
__asm__ __volatile__("movl %0, %%cr0"::"a" (cr0));
如果你指定⼀个缩写,⽐如字母a,则GCC将会根据当前操作表达式中C/C++表达式的宽度决定使⽤%eax,还是%ax或%al。⽐如:
unsigned short __shrt;
__asm__ ("mov %0,%%bx" : : "a"(__shrt));
由于变量__shrt是16-bit short类型,则编译出来的汇编代码中,则会让此变量使⽤%ex寄存器。编译结果为:
movw -2(%ebp), %ax # %ax = __shrt
#APP
movl %ax, %bx
#NO_APP
⽆论是Input,还是Output操作表达式约束,都可以使⽤寄存器约束。
下表中列出了常⽤的寄存器约束的缩写。
约束 Input/Output 意义
r I,O 表⽰使⽤⼀个通⽤寄存器,由GCC在%eax/%ax/%al, %ebx/%bx/%bl, %ecx/%cx/%cl, %edx/%dx/%dl中选取⼀个GCC认为合适的。
q I,O 表⽰使⽤⼀个通⽤寄存器,和r的意义相同。
a I,O 表⽰使⽤%eax / %ax / %al
b I,O 表⽰使⽤%ebx / %bx / %bl
c I,O 表⽰使⽤%ecx / %cx / %cl
d I,O 表⽰使⽤%edx / %dx / %dl
D I,O 表⽰使⽤%edi / %di
S I,O 表⽰使⽤%esi / %si
f I,O 表⽰使⽤浮点寄存器
t I,O 表⽰使⽤第⼀个浮点寄存器
u I,O 表⽰使⽤第⼆个浮点寄存器
2、内存约束
如果⼀个Input/Output操作表达式的C/C++表达式表现为⼀个内存地址,不想借助于任何寄存器,则可以使⽤内存约束。⽐如:
__asm__ ("lidt %0" : "=m"(__idt_addr)); 或 __asm__ ("lidt %0" : :"m"(__idt_addr));
我们看⼀下它们分别被放在⼀个C源⽂件中,然后被GCC编译后的结果:
cat example5.c // 本例中,变量sh被作为⼀个内存输⼊ int main(int __argc, char* __argv[]) { char* sh = (char*)&__argc; __asm__ __volatile__("lidt %0" : : "m" (sh)); return 0; } gcc -S example5.c
catexample5.smain:pushl4, %esp
leal 8(%ebp), %eax
movl %eax, -4(%ebp) # sh = (char*) &__argc
#APP
lidt -4(%ebp)
#NO_APP
movl 0, cat example6.c
// 本例中,变量sh被作为⼀个内存输出
int main(int __argc, char* __argv[])
{
char* sh = (char*)&__argc;
__asm__ __volatile__("lidt %0" : "=m" (sh));
return 0;
}
gcc−Sexample6.c cat example6.s
main:
pushl %ebp
movl %esp, %ebp
subl 4,0, %eax
leave
ret
⾸先,你会注意到,在这两个例⼦中,变量sh没有借助任何寄存器,⽽是直接参与了指令lidt的操作。
其次,通过仔细观察,你会发现⼀个惊⼈的事实,两个例⼦编译出来的汇编代码是⼀样的!虽然,⼀个例⼦中变量sh作为输⼊,⽽另⼀个例⼦中变量sh作为输出。这是怎么回事?
原来,使⽤内存⽅式进⾏输⼊输出时,由于不借助寄存器,所以GCC不会按照你的声明对其作任何的输⼊输出处理。GCC只会直接拿来⽤,究竟对这个C/C++表达式⽽⾔是输⼊还是输出,完全依赖与你写在"Instruction List"中的指令对其操作的指令。
由于上例中,对其操作的指令为lidt,lidt指令的操作数是⼀个输⼊型的操作数,所以事实上对变量sh的
操作是⼀个输⼊操作,即使你把它放在 Output域也不会改变这⼀点。所以,对此例⽽⾔,完全符合语意的写法应该是将sh放在Input域,尽管放在Output域也会有正确的执⾏结果。
所以,对于内存约束类型的操作表达式⽽⾔,放在Input域还是放在Output域,对编译结果是没有任何影响的,因为本来我们将⼀个操作表达式放在 Input域或放在Output域是希望GCC能为我们⾃动通过寄存器将表达式的值输⼊或输出。既然对于内存约束类型的操作表达式来说,GCC不会⾃动为它做任何事情,那么放在哪⼉也就⽆所谓了。但从程序员的⾓度⽽⾔,为了增强代码的可读性,最好能够把它放在符合实际情况的地⽅。
约束 Input/Output 意义
m I,O 表⽰使⽤系统所⽀持的任何⼀种内存⽅式,不需要借助寄存器
3、⽴即数约束
如果⼀个Input/Output操作表达式的C/C++表达式是⼀个数字常数,不想借助于任何寄存器,则可以使⽤⽴即数约束。
由于⽴即数在C/C++中只能作为右值,所以对于使⽤⽴即数约束的表达式⽽⾔,只能放在Input域。
⽐如:__asm__ __volatile__("movl %0, %%eax" : : "i" (100) );
⽴即数约束很简单,也很容易理解,我们在这⾥就不再赘述。
约束 Input/Output 意义
i I 表⽰输⼊表达式是⼀个⽴即数(整数),不需要借助任何寄存器
F I 表⽰输⼊表达式是⼀个⽴即数(浮点数),不需要借助任何寄存器
4、通⽤约束
约束 Input/Output 意义
g I,O 表⽰可以使⽤通⽤寄存器,内存,⽴即数等任何⼀种处理⽅式。
0,1,2,3,4,5,6,7,8,9 I 表⽰和第n个操作表达式使⽤相同的寄存器/内存。
通⽤约束g是⼀个⾮常灵活的约束,当程序员认为⼀个C/C++表达式在实际的操作中,究竟使⽤寄存器⽅式,还是使⽤内存⽅式或⽴即数⽅式并⽆所谓时,或者程序员想实现⼀个灵活的模板,让GCC可以根据不同的C/C++表达式⽣成不同的访问⽅式时,就可以使⽤通⽤约束g。⽐如:
#define JUST_MOV(foo) __asm__ ("movl %0, %%eax" : : "g"(foo))
JUST_MOV(100)和JUST_MOV(var)则会让编译器产⽣不同的代码。
int main(int __argc, char* __argv[])
{
JUST_MOV(100);
return 0;
}
编译后⽣成的代码为:
main:
pushl %ebp
movl %esp, %ebp
#APP
movl 100,0, %eax
popl %ebp
c语言编译器app怎么用ret
很明显这是⽴即数⽅式。⽽下⼀个例⼦:
int main(int __argc, char* __argv[])
{
JUST_MOV(__argc);
return 0;
}
经编译后⽣成的代码为:
main:
pushl %ebp
movl %esp, %ebp
#APP
movl 8(%ebp), %eax
#NO_APP
movl 0,12, %esp
movl 8,−4(4, -8(%ebp)
movl 3,−12(0, %eax
leave
ret
为了避免这种情况,我们必须向GCC声明这⼀点,要求GCC为所有的Input操作表达式指定别的寄存器,
⽅法就是在Output操作表达式"=a" (__out)的操作约束中加⼊&约束,由于GCC规定等号(=)约束必须放在第⼀个,所以我们写作"=&a"(__out)。
下⾯是我们将&约束加⼊之后编译的结果:
main:
pushl %ebp
movl %esp, %ebp
subl 12,8, -4(%ebp)
movl 4,−8(3, -12(%ebp)
movl -4(%ebp), %edx #__in1使⽤寄存器%edx
movl -8(%ebp), %eax
movl %eax, %ecx # __in2使⽤寄存器%ecx
#APP
popl %eax
movl %edx, %esi
movl %ecx, %edi
#NO_APP
movl %eax, %eax
movl %eax, -12(%ebp) #__out使⽤寄存器%eax
movl 0, cat example7.c
int main(int __argc, char* __argv[])
{
int in = 8;
__asm__ ("addl %0, %%ebx"
:
/* no output */
: "a" (in) : "bx");
return 0;
}
gcc−O−Sexample7.c cat example7.s
main:
pushl %ebp
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论