C语言内联汇编

之前我们介绍了一种C语言与汇编代码混合编程方式,就是两个文件分开编写,分开编译,最后通过链接的形式结合在一起形成可执行文件,另一种方式就是C语言内联汇编,这一切都要归功于强大的GCC。

什么是内联汇编

内联汇编称为inline assembly,GCC支持在C代码中直接嵌入汇编代码,所以称为GCC inline assembly。大家知道,C语言不支持寄存器操作,汇编语言可以,所以自然就想到了在C语言中嵌入内联汇编提升“战斗力”的方式,通过内联汇编,C程序员可以实现C语言无法表达的功能,这样使开发能力大为提升。

内联汇编按格式分为两大类,一类是最简单的基本内联汇编,另一类是复杂一些的扩展内联汇编,在介绍它们之前,其实还有一点点头疼的事,内联汇编中所用的汇编语言,其语法是AT&T,并不是咱们熟悉的Intel汇编语法,GCC只支持它,所以咱们还得了解下AT&T。

AT&T语法

我之前学的一门课,微机原理教的就是Intel语法,发现身边的汇编语言什么的教的也都是Intel语法,也许这和教学系统都是微软的操作系统DOS和Windows有关。其实不论是Intel语法或者是AT&T语法,其在某一个平台上编译出来的机器码是一样的,其本质没有区别,只是表达方式有点区别。

AT&T首先在UNIX中使用,可当初UNIX并不是在x86处理器上开发的,最初是在PDP-11机器上开发的,后来又移植到VA X和68000的处理器上,所以AT&T的语法自然更接近于这些处理器的特性。虽然UNIX后来又移植到x86上了,但还是要尊重UNIX圈内的习惯,其汇编语法接近于那些前辈处理器上的语法,这就是AT&T语法。

无论语法怎么改变,汇编语言中指令关键字不能有太大的出入,名字非常接近,只是在指令名字的最后加上了操作数大小后缀,b表示一字节,w表示二字节,l表示四字节,比如压栈指令,在Intel语法中是 push ,在AT&T语法中是 pushl ,最后这个l表示要压栈的数据长度是4字节,在了解Intel语法的情况下,基本能看懂AT&T语法,他们的主要差别是语法风格

请添加图片描述

上表未列出两种语法在内存寻址之间的区别,我们详细说一下

在Intel语法中,立即数就是普通的数字,如果让立即数成为内存地址,需要将他用中括号括起来,才能表示以立即数为地址的内存

而AT&T认为,内存地址既然是数字,那数字理所应当的也应该被当做内存地址,所以数字优先被认为是内存地址,也就是说,操作数如果是数字,则统统按照以该数字为地址的内存来访问。这样的话我们想要单纯的表示立即数就要在前面加一个 $

Intel语法有很多的寻址方式,包括直接寻址,基址寻址,变址寻址,基址变址寻址,而且不知道是不是开始学的时候就是Intel语法,所以觉得Intel语法是很直接的一种寻址方式。

在AT&T中内存寻址有着固定的格式

segreg(段基址):base_address(offset_address,index,size)

此格式对应的表达式为

segreg(段基址):base_address+offset_address+index*size)

看上去格式有些怪异,但其实这是一种“通用”格式,格式中短短的几个成员囊括了它所有内存寻址的方式,任意一种内存寻址方式,其格式都是这个通用格式的子集,都是格式中各种成员的组合。下面介绍下这些成员项。

base_address是基地址,可以为整数、变量名,可正可负。

offset_address是偏移地址,index是索引值,这两个必须是那8个通用寄存器之一。

size是个长度,只能是1、2、4、8。

下面看看内存寻址中有哪些方式,注意,这些方式都是上面通用格式的一部分。

直接寻址:此寻址中只有base_address项,后面括号中的内容全不要,base_address便为内存啦,比如 movl $225,0xc00008F0 ,或者用变量名 mov $6,var

寄存器间接寻址: 此寻址中只有offset_address项,即格式为(offset_address),要记得,offset_address只能是通用寄存器。寄存器中是地址,不要忘记格式中的圆括号,如mov (%eax),%ebx

**寄存器相对寻址:**此寻址中有offset_address项和base_address项,即格式为base_address(offset_address)。这样得出的内存地址是基址+偏移地址之和。如movb -4(%ebx),%al

**变址寻址:**此类寻址称为变址的原因是含有通用格式中的变量Index。因为index是size的倍数,所以有index的地方就有size。既然是变址,只要有index和size就成了,base_address和offset_address可有可无,注意,格式中没有的部分也要保留逗号来占位。一共有4种变址寻址组合,下面各举个例子。

  • 无base_address,无offset_address:movl %eax,(,%esi,2)

  • 无base_address,有offset_address:movl %eax,(%ebx,%esi,2)

  • 有base_address,无offset_address:movl %eax,base_value(,%esi,2)

  • 有base_address,有offset_address:movl %eax,base_value(%ebx,%esi,2)

基本内联汇编

基本内联汇编是最简单的内联形式,其格式为:

asm [volatile] ("assembly code")  

关键字asm用于声明内联汇编表达式,这是内联汇编固定的部分,不可少。

asm__asm__是一样的,是由gcc定义的宏:#define __asm__ asm。gcc有一个优化选项 -O ,可以指定优化级别,当用 -O 来编译的时候,gcc按照自己的意图优化代码,说不定就会把自己写的代码改了(他认为你写的太烂了), 关键字volatile是可选项,它告诉gcc:“不要修改我写的汇编代码,请原样保留”,volatile__volatile__是一样的,是由gcc定义的宏:#define __volatile__ volatile

“assembly code”是咱们所写的汇编代码,它必须位于圆括号中,而且必须用双引号引起来。这是格式要求,只要满足了这个格式asm [volatile] (“”),assembly code甚至可以为空。

下面说下assembly code的规则。

  • 指令必须用双引号引起来,无论双引号中是一条指令或多条指令。
  • 一对双引号不能跨行,如果跨行需要在结尾用反斜杠’\'转义。

提醒一下,即使是指令分布在多个双引号中,gcc最终也要把它们合并到一起来处理,合并之后,指令间必须要有分隔符。所以,当指令在多个双引号中时,除最后一个双引号外,其余双引号中的代码最后一定要有分隔符,这和其他编程语言中表示代码结束的分隔符是一样的,如:

asm("movl $9,%eax;","pushl %eax") # 正确
asm("movl $9,%eax","pushl %eax")  # 错误

我们再举一个打印字符串的例子

char *str = "hello ASM C!";
int count = 0;
int main(){
	asm( "pusha;\
		movl $4,%eax;\
		movl $1,%ebx;\
		movl str,%ecx;\
		movl $12,%edx;\
		int $0x80;\
		mov %eax,count;\
		popa \
	");
	return 0;
}

在上述代码中,若要内联汇编引用c变量,只能将c变量定义为全局变量,只有定义为全局变量的时候才能链接这两个符号,如果定义为局部变量,链接时会找不到这两个符号,这就是基本的内联汇编,我们编译运行一下

gcc -m32 -o inline.bin inline.c
./inline.bin

扩展内联汇编

由于基本内联汇编功能太薄弱了,所以才对它进行了扩展以使其功能强大。不过,易用性往往与功能强弱是成正比的,如您所料,扩展内联汇编确实有点难。

gcc本身是个c编译器,要让其支持汇编语言,必然牵扯到以下问题。

  • 在内联汇编代码插入点之前的C代码,其编译后也要被分配寄存器等资源,插入的汇编代码也要使用寄存器,这是否会造成资源冲突?
  • 汇编语言如何访问C代码中的变量?

您看,内联汇编真不是简单地写两句汇编代码就完事了,所以,很多人宁可单独写纯汇编文件再链接,也不愿意写内联汇编。

假设目前没有扩展内联汇编,当汇编代码嵌入到C代码中,如果汇编代码想把C代码中的变量作为操作数加载到寄存器,如何找到可用的寄存器,这可是个大问题,程序员并不知道哪个寄存器已经被分配了,哪些寄存器是空闲的。即使知道了寄存器的分配情况也还不够,有些底层操作,对寄存器的要求是固定的(比如in/out指令,就得使用al作为数据寄存器),万一那个固定的寄存器已经被占用了,咱们在使用前还得把它备份。

也许你觉得不难啊,我之前在c中使用了那些寄存器,我现在在内联汇编中用那些寄存器就先将其入栈备份,用完了再恢复,但是让用户自己保证数据的完整性会出大问题,万一漏掉一个寄存器,就完啦!!!,程序就不知道会运行到哪里,再说,运行中有大量的压栈操作,访问内存本身就比较慢,不如在编译阶段由编译器优化,直接分配给寄存器或用寄存器缓存,这样程序运行才更快。所以,这类事情还是交给编译器自己做这事才放心。

既然编译器不放心,那么这件事情就变成了如何将C代码中的变量编程汇编代码中的操作数,由于编译器无法预测用户的需求,所以编译器向用户提供了一个模板,让用户在模板中提出要求,其余工作他负责实现,这些用户提出的要求就是后面说的约束。

因此,内联汇编的格式也变了,感觉既不像C语言,也不像汇编语言,似乎是一种中间产物,不信您看。

asm [volatile] (“assembly code”:output : input : clobber/modify) 

和前面的基本内联汇编相比,扩展内联汇编在圆括号中变成了4部分,多了output、input和clobber/modify三项。其中的每一部分都可以省略,甚至包括assembly code。

assembly code:还是用户写入的汇编指令,和基本内联汇编一样。

output:用来指定汇编代码的数据如何输出给C代码使用。如果想将汇编运行结果存储到c变量中,就用此项指定输出的位置。output中每个操作数的格式为:

“操作数修饰符约束名” (c变量名)

其中的引号和圆括号不能少,操作数修饰符通常为等号’=‘。多个操作数之间用逗号’,'分隔。

input:用来指定C中数据如何输入给汇编使用,input中每个操作数的格式为:

“[操作数修饰符]约束名” (c变量名)

其中的引号和圆括号不能少,操作数修饰符为可选项。多个操作数之间用逗号’,'分隔。

clobber/modify:汇编代码执行后会破坏一些内存或寄存器资源,通过此项通知编译器,可能造成寄存器或内存数据的破坏,这样gcc就知道哪些寄存器或内存需要提前保护起来。

约束

上面所说的“要求”,在扩展内联汇编中称为“约束”,它所起的作用就是把C代码中的操作数(变量、立即数)映射为汇编中所使用的操作数,实际就是描述C中的操作数如何变成汇编操作数。这些约束的作用域是input和output部分,咱们看看这些约束是怎么体现的,约束分为四大类。

寄存器约束

寄存器约束就是要求gcc使用哪个寄存器,将input或者output中变量约束在某个寄存器中,常见的寄存器约束有

  • a:表示寄存器eax/ax/al
  • b:表示寄存器ebx/bx/bl
  • c:表示寄存器ecx/cx/cl
  • d:表示寄存器edx/dx/dl
  • D:表示寄存器edi/di
  • S:表示寄存器esi/si
  • q:表示任意这4个通用寄存器之一:eax/ebx/ecx/edx
  • r:表示任意这6个通用寄存器之一:eax/ebx/ecx/edx/esi/edi
  • g:表示可以存放到任意地点(寄存器和内存)
  • A:把eax和edx组合成64位整数
  • f:表示浮点寄存器
  • t:表示第1个浮点寄存器
  • u:表示第2个浮点寄存器

我们来感受一下基本内联汇编和扩展内联汇编之间的区别

基本内联汇编

#include<stdio.h>
int in_a=1,in_b=2,out_sum;
int main(){
	asm("pusha;\
		movl in_a,%eax;\
		movl in_b,%ebx;\
		addl %ebx,%eax;\
		movl %eax,out_sum;\
		popa;\
	");
    printf("%d\n",out_sum);
    return 0;
}

扩展内联汇编

#include <stdio.h>
int main(){
	int in_a=1,in_b=2,out_sum;
	asm("addl %%ebx,%%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));
	printf("%d\n",out_sum);
}

编译链接执行可以打印字符串 3

同样是为加法指令提供参数,in_a和in_b是在input部分中输入的,用约束名a为c变量in_a指定了用寄存器eax,用约束名b为c变量in_b指定了用寄存器ebx。addl指令的结果存放到了寄存器eax中,在output中用约束名a指定了把寄存器eax的值存储到c变量out_sum中。output中的’='号是操作数类型修饰符,表示只写,其实就是out_sum=eax的意思。

通过对比我们发现他的功能确实强大了很多,我们只需要把数据往里面放,剩下的交给GCC。

内存约束

内存约束是要求gcc直接将位于input和output中的C变量的内存地址作为内联汇编代码的操作数,不需要寄存器做中转,直接进行内存读写,也就是汇编代码的操作数是C变量的指针。

  • m:表示操作数可以使用任意一种内存形式。
  • o:操作数为内存变量,但访问它是通过偏移量的形式访问,即包含offset_address的格式。

我们依旧是举个例子

#include<stdio.h>
int main(){
	int in_a=1,in_b=2;
	asm("movb %b0, %1;"::"a"(in_a),"m"(in_b));
	printf("%d\n",in_b);
}

编译运行一下

gcc -m32 -o in.bin in.c
./in.bin

我们发现控制台可以输出 1

立即数约束

立即数即常数,此约束要求gcc在传值的时候不通过内存和寄存器,直接作为立即数传给汇编代码。由于立即数不是变量,只能作为右值,所以只能放在input中。

  • i:表示操作数为整数立即数
  • F:表示操作数为浮点数立即数
  • I:表示操作数为0~31之间的立即数
  • J:表示操作数为0~63之间的立即数
  • N:表示操作数为0~255之间的立即数
  • O:表示操作数为0~32之间的立即数
  • X:表示操作数为任何类型

立即数为节约篇幅,后面将立即数约束同其他约束一起演示,这里没有单独样例。

通用约束

0~9:此约束只用在input部分,但表示可与output和input中第n个操作数用相同的寄存器或内存。

占位符

为方便对操作数的引用,扩展内联汇编提供了占位符,它的作用是代表约束指定的操作数(寄存器、内存、立即数),我们更多的是在内联汇编中使用占位符来引用操作数。

符号占位符

序号占位符是对在output和input中的操作数,按照它们从左到右出现的次序从0开始编号,一直到9,也就是说最多支持10个序号占位符。

操作数用在assembly code中,引用它的格式是%0~9。在操作数自身的序号前面加1个百分号’%'便是对相应操作数的引用。一定要切记,占位符指代约束所对应的操作数,也就是在汇编中的操作数,并不是圆括号中的C变量。

我们举个例子

asm("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));

等价于

asm("addl %2, %1":"=a"(out_sum):"a"(in_a),"b"(in_b)); 

"=a"(out_sum)序号为0,%0对应的是eax。

"a"(in_a)序号为1,%1对应的是eax。

"b"(in_b)序号为2,%2对应的是ebx。

由于扩展内联汇编中的占位符要有前缀%,为了区别占位符和寄存器,只好在寄存器前用两个%做前缀啦,这就是为什么本例中寄存器前面要有两个%做前缀的原因。

占位符所表示的操作数默认情况下为32位数据。指令的操作数大小并不一致,有的指令操作数大小是32位,有的是16位,有的是8位。当为这些指令提供操作数时,编译器会自动取32位数据的低16位给需要16位操作数的指令,取32位的低8位给需要8位操作数的指令。由于32位数据中,高16位没法直接使用,所以对于16位操作数只能取32位中的低16位。但对于8位操作数就不一样了,尽管默认情况下会用低8位(0~7位)作为字节指令的操作数,但32位数据中能直接使用的字节不只是低8位,还有第8~15位。拿32位的寄存器eax举例,其常用的部分是eax、ax、al(高16位没法直接用)。有些指令的操作数是字,所以用ax做操作数即可。有些指令操作数是字节,用al或ah都可以,默认情况下会将al当作操作数。这时候我们可以在%和序号之间插入字符’h’来表示操作数为ah(第8~15位),或者插入字符’b’来表示操作数为al(第0~7位)。

举个例子

#include <stdio.h>
int main() {
	int in_a = 0x12345678, in_b = 0;
	
	asm("movw %1, %0;":"=m"(in_b):"a"(in_a)); 
	printf("word in_b is 0x%x\n", in_b); 
	in_b = 0;
	
	asm("movb %1, %0;":"=m"(in_b):"a"(in_a)); 
	printf("word in_b is 0x%x\n", in_b); 
	in_b = 0;
	
	asm("movb %h1, %0;":"=m"(in_b):"a"(in_a)); 
	printf("word in_b is 0x%x\n", in_b); 
	
	return 0;
}

我们编译链接的时候报了异常

in.c: Assembler messages:
in.c:5: Warning: using `%ax' instead of `%eax' due to `w' suffix
in.c:9: Warning: using `%al' instead of `%eax' due to `b' suffix

可见其非常智能哈,执行一下得到结果

word in_b is 0x5678     # 传入一个字,默认低16位
word in_b is 0x78       # 传入一个字节,默认低8位al
word in_b is 0x56       # 传入一个字节,拉到高8位ah
名称占位符

名称占位符与序号占位符不同,序号占位符靠本身出现在output和input中的位置就能被编译器辨识出来。而名称占位序需要在output和input中把操作数显式地起个名字,它用这样的格式来标识操作数:

[名称]”约束名”(C变量)

我们举个例子

#include <stdio.h>
int main(){
	int in_a = 18,in_b=3,out=0;
    asm("divb %[divisor];movb %%al,%[result]"\
       :[result]"=m"(out)\
       :"a"(in_a),[divisor]"m"(in_b)\
       );
    printf("result is %d\n",out);
    return 0;
}

编译执行结果为6,当然这没什么说的。 可以看到第4行除法指令divb可以通过%[divisor]引用除数所在的内存,进行除法运算,个人认为没有符号占位符方便,毕竟起名称是程序员最难得工作。无论是哪种占位符,它都是指代C变量经过约束后、由gcc分配的对应于汇编代码中的操作数,和C变量本身无关。这个操作数就是通过约束名所指定的寄存器、内存、立即数等,最终编译器要将占位符转换成这三种操作数类型之一。

操作数类型修饰符

在约束中还有操作数类型修饰符,用来修饰所约束的操作数:内存、寄存器,分别在ouput和input中有以下几种。

在output中有三种

=:表示操作数是只写,相当于为output括号中的C变量赋值,如=a(c_var),此修饰符相当于c_var=eax。

+:表示操作数是可读写的,告诉gcc所约束的寄存器或内存先被读入,再被写入。

&:表示此output中的操作数要独占所约束(分配)的寄存器,只供output使用,任何input中所分配的寄存器不能与此相同。注意,当表达式中有多个修饰符时,&要与约束名挨着,不能分隔。

在input中有一种

%:该操作数可以和下一个输入操作数互换。

clobber/modify

这一部分是扩展内联汇编的最后一部分,这部分用于通知gcc,我们修改了哪些寄存器或内存。

由于我们在C程序中嵌入了一些汇编代码,所以这必然会造成一些资源的破坏,本来人家C代码翻译之后也要用到寄存器,突然来了一堆抢寄存器的汇编指令,从c跳到汇编,再从汇编跳到C的过程可能会导致之前在寄存器中的信息消失,或者保存在内存中的信息消失,所以我们得让GCC知道我们改变了那些寄存器或内存

如果在output和input中通过寄存器约束指定了寄存器,GCC必然会知道这些寄存器会被修改,所以需要在这个单独的部分通知的寄存器一定是没在之前出现过的。

也许您会认为,牵扯到修改寄存器或内存的部分,只差assembly code了,这部分GCC可以自己扫描一下啊,为什么要我们显式的指出呢,问题是GCC能扫描到明处的指令,我使用了哪个寄存器,但是暗处的他就无能为力了,比如在汇编中调用了一个函数,该函数内部会修改一些资源,或者该函数中又调用了其他函数,这保不准在哪一层调用有修改资源的代码,简直无法跟踪(不过也说不定,万一以后GCC会有强大的前后文联系能力与推理能力呢)。所以必须我们显式的告诉GCC我们动了那些资源,这个资源就是寄存器和内存。

只要在clobber/modify部分明确写出来就行了,记得要用双引号把寄存器名称引起来,多个寄存器之间用逗号’,‘分隔,这里的寄存器不用再加两个’%'啦,只写名称即可,如:

asm("movl %%eax, %0;movl %%eax,%%ebx":"=m" (ret_value)::"bx") 

这里就指出了我们使用了bx寄存器。

如果我们的内联汇编代码修改了标志寄存器eflags中的标志位,同样需要在clobber/modify中用”cc”声明。

如果我们修改了内存,我们需要在clobber/modify中”memory”声明。

使用memory的原因还有一个就是清除寄存器缓存,我们知道一个值被使用过了就是被缓存到寄存器缓存中,因为根据局部性原理,一个值被使用了就有很大概率还会被使用,缓存可以加快访问速度,但是我们这个值在内存中发生了改变时,还使用缓存中的值的话就会出问题,所以这个时候C语言有关键字 volatile ,他表示这个值不需要被缓存,直接去内存中读取,这就避免了这个问题,但汇编中的volatile是定义的宏 #define __volatile__ volatile这和C语言中的volatile不冲突。利用这个原理,不管变量的值是否会被编译器缓存到寄存器中,当我们需要绕过寄存器缓存,也就是希望读取到内存中最新的数据时,我们就可以在内联汇编中的clobber/modify部分用”memory”声明,通知编译器变量所在的内存数据变啦,这样它就会从内存再读取一次新数据啦。当然我们也可以在C语言中用volatile去修饰所定义的变量,但是变量多了就有些麻烦

扩展内联汇编之机器模式

在前面介绍序号占位符的时候,咱们已经引出了机器模式的内容:为了指定寄存器中的某部分,咱们引用了字符’h’和字符’b’,它们分别用来指定寄存器的第8~15位和低8位,这只是机器模式的用途之一。

比如寄存器约束a表示寄存器al、ax、eax,可以在序号占位符中增加前缀字符h和b来引用寄存器ah和al。不过这次我不想指定ah或al啦,如果我想指定ax或eax,怎么做?为了回答这个问题,咱们再举个例子

#include <srdio.h>
int main(){
	int in_a = 0x1234, in_b = 0;
	asm("movw %1,%0":"=m"(in_b):"a"(in_a));
	printf("in_b now is 0x%x\n",in_b);
}

这段代码很简单,目的是把in_a的低16位复制到in_b中。但是第四行中,变量in_a的约束时a,这表示由GCC把in_a的值分配给寄存器 AL、AX或EAX。这很模糊,到底GCC把in_a的值分配给了谁?之后的movw指令也很模糊,我们只能这样理解:movw指令将al、ax或eax中的2个字节复制到in_b所在的内存中。我们编译一下,报了异常

in.c: Assembler messages:
in.c:4: Warning: using `%ax' instead of `%eax' due to `w' suffix

他帮我们从ax寄存器换成了eax寄存器,但是在实际代码中我们并没有添加w,这说明默认情况下,GCC用占位符引用操作数的时候,根据指令操作数大小的不同,添加了适当的前缀。我们实验一下

#include <srdio.h>
int main(){
	int in_a = 0x1234, in_b = 0;
	asm("movw %w1,%0":"=m"(in_b):"a"(in_a));
	printf("in_b now is 0x%x\n",in_b);
}

这次编译的过程没报任何错误,这里的字符w是什么意思呢?这是我们新接触的控制字符。

w和h、b一样,都是操作码,用来指代某种机器模式类型。

我们看一下权威的解释

/* Meaning of CODE:
     L,W,B,Q,S,T -- print the opcode suffix for specified size of operand. 
     C -- print opcode suffix for set/cmov insn. 
     c -- like C, but print reversed condition 
     F,f -- likewise, but for floating-point. 
     O -- if HAVE_AS_IX86_CMOV_SUN_SYNTAX, expand to "w.", "l." or "q." otherwise nothing 
     R -- print embeded rounding and sae. 
     r -- print only sae. 
     z -- print the opcode suffix for the size of the current operand. 
     Z -- likewise, with special suffixes for x87 instructions. 
     * -- print a star (in certain assembler syntax) 
     A -- print an absolute memory reference. 
     E -- print address with DImode register names if TARGET_64BIT. 
     w -- print the operand as if it's a "word" (HImode) even if it isn't. 
     s -- print a shift double count, followed by the assemblers argument delimiter. 
     b -- print the QImode name of the register for the indicated operand. %b0 would print %al if operands[0] is reg 0. 
     w --  likewise, print the HImode name of the register. 
     k --  likewise, print the SImode name of the register. 
     q --  likewise, print the DImode name of the register. 
     x --  likewise, print the V4SFmode name of the register. 
     t --  likewise, print the V8SFmode name of the register. 
     g --  likewise, print the V16SFmode name of the register. 
     h -- print the QImode name for a "high" register, either ah, bh, ch or dh. 
     y -- print "st(0)" instead of "st" as a register. 
     d -- print duplicated register operand for AVX instruction. 
     D -- print condition for SSE cmp instruction. 
     P -- if PIC, print an @PLT suffix. 
     p -- print raw symbol name.
     X -- don't print any sort of PIC '@' suffix for a symbol. 
     & -- print some in-use local-dynamic symbol name. 

机器模式用在机器层面上指定数据的大小及格式,用我自己的理解是GCC支持内联汇编,由于各种约束均不能确切地表达具体的操作数对象,所以引用了机器模式,用来从更细的粒度上描述数据对象的大小及其指定部分。GCC根据不同的硬件平台,将机器模式定义在多个文件中,其中所有平台都通用的机器模式定义在gcc/machmode.def文件中,其他与具体平台相关的机器模式定义在自己的平台路径下。

其实我们只需要初步了解几个操作码就够了,寄存器按是否可单独使用,可分成几个部分,拿eax举例。

低部分的一字节:al
高部分的一字节:ah
两字节部分:ax
四字节部分:eax

h –输出寄存器高位部分中的那一字节对应的寄存器名称,如ah、bh、ch、dh。
b –输出寄存器中低部分1字节对应的名称,如al、bl、cl、dl。
w –输出寄存器中大小为2个字节对应的部分,如ax、bx、cx、dx。
k –输出寄存器的四字节部分,如eax、ebx、ecx、edx。

这一块就不深究了,深究也不会了 😃

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CLion 是 JetBrains 公司开发的一款集成开发环境 (IDE),主要用于 C 和 C++ 编程。在 CLion 中,内汇编(Inline Assembler)允许开发者在编写 C 或 C++ 代码时直接嵌入汇编指令,以利用硬件级别的性能优化或者处理特定平台特有的低级操作。 以下是使用 CLion 进行内汇编的一些关键步骤和注意事项: 1. **启用内汇编支持**:确保你的 CLion 配置支持内汇编。在“Build, Execution, Deployment”(构建、执行和部署)菜单中,选择“CMake”,然后在生成器的配置中启用对 inline assembly 的支持。 2. **插入汇编代码**:在需要使用内汇编的地方,通常使用 `asm` 关键字,后跟括号括起的汇编指令。例如: ```cpp int x = 0; asm volatile ("movl %1, %0" : "=r"(x) : "r"(42)); ``` 3. **声明和管理内存**:内汇编需要手动管理内存,使用 `%` 符号指定寄存器或内存地址。例如,`%eax` 表示 EAX 寄存器,`%edi` 表示 EDI 寄存器,`%esp` 则是栈指针。 4. **调试和检查**:由于内汇编不被编译器完全理解和跟踪,可能需要使用汇编查看器或者断点来检查执行过程。CLion 提供了一些辅助工具,如 Disassembly view(反汇编视图)。 5. **注意兼容性和限制**:不是所有处理器架构都支持内汇编,且不同编译器对它的处理方式可能不同。务必确保你的目标平台支持,并在编译时正确设置目标架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LyaJpunov

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值