b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

C语言asm __volatile__ [转移]的GCC内联汇编

电脑杂谈  发布时间:2020-05-26 07:17:45  来源:网络整理

c语言对应汇编语句_c语言程序设计试题汇编答案_汇编语言和c语言

发件人:

在嵌入式汇编中,您可以将C语言表达式指定为汇编指令的操作数,而不必担心如何读取寄存器中的C语言表达式的值以及如何编写C语言表达式. 计算结果返回到C变量您只需告诉C语言表达式和程序中的汇编指令操作数之间的对应关系,GCC就会自动插入代码以完成必要的操作.

1. 简单的内联汇编

示例:

__ asm__ __volatile __(“暂停”); “ __asm__”表示以下代码是内联汇编,“ asm”是“ __asm__”的别名. “ __volatile__”表示编译器不会优化代码,以下说明保持不变,并且“ volatile”为其别名. 组装说明在括号中.

2. 内联汇编的例子

要使用内联汇编,必须首先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC这些操作受到哪些限制. 例如,在以下汇编语句中:

__ asm__ __violate __(“ movl%1,%0”: “ = r”(结果): “ m”(输入));

“ movl%1,%0”是指令模板; “%0”和“%1”表示指令的操作数,称为占位符,并且内联汇编程序依靠它们来转换C语言表达式和相应的指令操作数. 指令模板在C语言表达式后的括号内. 在此示例中,只有两个: “结果”和“输入”,它们按照出现顺序对应于指令操作数“%0”和“%1”. 注意相应的顺序: 第一个C表达式对应于“%0”;第二个表达式对应于“%1”,依此类推,最多10个操作数分别使用“%0”,“%1”. ...“%9”表示. 每个操作数前面都有一个用引号引起来的字符串,字符串的内容是操作数的限制或要求. “结果”前面的受限字符串是“ = r”,其中“ =”表示“结果”是输出操作数,“ r”表示“结果”需要与通用寄存器关联,首先是值读取寄存器中的操作数,然后在指令中使用相应的寄存器,而不是“结果”本身. 当然,在执行指令之后,寄存器中的值需要存储在变量“结果”中. 从表面上看,指令似乎直接执行了操作,实际上GCC已经执行了隐式处理,因此我们可以编写更少的指令. “输入”前面的“ r”表示需要先将表达式放入寄存器中,然后在指令中使用该寄存器参与操作.

C表达式或变量与寄存器之间的关系由GCC自动处理. 我们只需要使用限制字符串来指示GCC如何处理它. 限制字符必须符合指令的操作数要求,否则生成的汇编代码将是错误的,读者可以将上面示例中的两个“ r”更改为“ m”(m表示操作数位于内存中,而不是寄存器中),编译后的结果是:

移动输入,结果

很明显,这是一条非法指令,因此限制字符串必须符合指令的操作数要求. 例如,指令movl允许寄存器到寄存器,立即数到寄存器等,但不允许内存到内存操作,因此两个操作数不能同时使用“ m”作为限定符.

内联汇编语法如下:

__ asm __(汇编语句模板: 输出部分: 输入部分: 破坏描述部分)

总共四个部分: 汇编语句模板,输出部分,输入部分,销毁描述部分,每个部分用“: ”分隔,汇编语句模板是必不可少的,其他三个部分是可选的,如果后者使用,并且前面部分为空,还需要使用“: ”打开,对应的部分为空. 例如:

__ asm__ __volatile __(“ cli” :::“内存”)

1. 汇编语句模板

汇编语句模板由汇编语句序列组成. 使用“;”,“ \ n”或“ \ n \ t”来分隔语句. 指令中的操作数可以使用占位符来引用C语言变量. 操作数最多包含10个占位符,其名称如下: %0,%1,...,%9.指令中占位符表示的操作数始终被视为长(4字节),但应用于它们的操作可以是单词或字节,具体取决于指令. 当使用操作数作为单词或字节时,默认值为低位字或低位字节. 字节操作可以显式指示是低字节还是子字节. 方法是在%和序列号之间插入一个字母c语言对应汇编语句,“ b”代表低字节,“ h”代表高字节,例如: %h1.

2. 输出部分

输出部分描述输出操作数. 不同的操作数描述符用逗号分隔. 每个操作数描述符由合格的字符串和C语言变量组成. 每个输出操作数的限定字符串必须包含“ =”,以表明它是一个输出操作数.

示例:

__ asm__ __volatile __(“ pushfl; popl%0; cli”: “ = g”(x))

描述符字符串指示对变量的限制,以便GCC可以根据这些条件决定如何分配寄存器,以及如何生成必要的代码来处理指令操作数与C表达式或C变量之间的连接.

3. 输入部分

输入部分描述输入操作数. 不同的操作数描述符用逗号分隔. 每个操作数描述符都由限定的字符串和C语言表达式或C语言变量组成.

示例1:

__ asm__ __volatile__(“ lidt%0” ::“ m”(real_mode_idt));

示例二(bitops.h):

静态__inline__ void __set_bit(int nr,易失性void * addr)

{

__ asm __(

“ btsl%1,%0”

: “ = m”(ADDR)

: “ Ir”(nr));

}

汇编语言和c语言_c语言对应汇编语句_c语言程序设计试题汇编答案

示例函数是将(* addr)的nr位设置为1. 第一个占位符%0对应于C语言变量ADDR,第二个占位符%1对应于C语言变量nr. 因此,以上汇编语句代码等效于以下伪代码: btsl nr,ADDR,此指令的两个操作数不能全部是存储器变量,因此将nr的合格字符串指定为“ Ir”,并将nr和立即数指定为或寄存器是关联的,因此两个操作数中只有ADDR是存储变量.

4. 限制字符

4.1. 受限字符列表

有多种限制字符,其中一些与特定体系结构有关. 这只是i386中可能使用的常用限定符和一些常用限定符. 它们的作用是指导编译器如何处理后续C语言变量和指令操作数之间的关系.

类别预选赛说明

通用寄存器“ a”将输入变量放入eax

这里有一个问题: 如果已经使用了eax,该怎么办?

实际上,这非常简单: 因为GCC知道已经使用了eax,所以它在此汇编代码中

在句子开头插入一句pushl%eax,将eax的内容保存到堆栈中,然后

在此代码的末尾添加一条语句popl%eax以恢复eax的内容

“ b”将输入变量放入ebx

“ c”将输入变量放入ecx

“ d”将输入变量放入edx

“ s”将输入变量放入esi

“ d”将输入变量放入edi

“ q”将输入变量放入eax,ebx,ecx和edx之一

“ r”将输入变量放入通用寄存器,即eax,ebx,ecx,

edx,esi,edi之一

“ A”将eax和edx组合到一个64位寄存器中(使用长整型)

内存“ m”个内存变量

“ o”操作数是一个内存变量,但其寻址方法是偏移类型,

即基址或基址加索引地址

“ V”操作数是一个内存变量,但寻址模式不是偏移类型

“”“操作数是内存变量,但寻址模式是自动递增

“ p”操作数是一个合法的内存地址(指针)

寄存器或存储器“ g”将输入变量放入eax,ebx,ecx和edx之一

或作为存储变量

“ X”操作数可以是任何类型

立即

“ I”之间的立即数0-31(用于32位移位指令)

“ J” 0-63之间的立即数(用于64位移位指令)

“ N”立即值介于0-255之间(用于out命令)

“我”立即

“ n”个即时数据,某些系统不支持单词以外的即时数据,

这些系统应使用“ n”代替“ i”

c语言程序设计试题汇编答案_汇编语言和c语言_c语言对应汇编语句

匹配“ 0”表示受其限制的操作数与指定的操作数匹配,

“ 1” ...,即操作数是指定的操作数,例如“ 0”

“ 9”描述“%1”操作数,然后“%1”实际上是指它

是“%0”操作数,请注意0-9和

作为限定词

指令中“%0”和“%9”之间的区别,前者描述了操作数,

后者代表操作数.

&输出操作数不能使用与输入操作数相同的寄存器

操作数为“ =”,操作数在指令(输出操作数)中为只写操作

“ +”操作数在指令中是读写类型(输入和输出操作数)

浮点数“ f”浮点寄存器

“ t”第一个浮点寄存器

“ u”个第二个浮点寄存器

“ G”标准的80387浮点常数

%该操作数可以与下一个操作数交换位置

例如,addl的两个操作数可以交换

(当然,两个操作数都不能是立即数)

#一些注释,从该字符到逗号后的所有字母都将被忽略

*表示如果选择了寄存器,则会忽略其后的字母

5. 破坏描述部分

破坏描述符用于通知编译器我们使用了哪些寄存器或内存. 它由逗号分隔的字符串组成. 每个字符串描述一个情况,通常是一个寄存器名称. 除了寄存器外,还有“内存”. 例如: “%eax”,“%ebx”,“内存”等.

“内存”很特殊,可能是内联汇编中最困难的部分. 为了清楚地解释它,首先介绍编译器的优化知识,然后查看C关键字volatile. 最后,查看描述符.

1. 编译器优化简介

内存访问速度远远小于CPU处理速度. 为了提高计算机的整体性能,将硬件高速缓存引入硬件中以加快对内存的访问. 另外,在现代CPU中,不必严格按顺序执行指令的执行,并且可以不按顺序执行没有相关性的指令,从而充分利用CPU的指令流水线并提高执行速度. 以上是硬件级别的优化. 看一下软件级别的优化: 一种是由程序员在编写代码时优化的,另一种是由编译器优化的. 编译器优化的常用方法是: 将内存变量缓存到寄存器中;调整指令顺序以充分利用CPU指令流水线,通常对读取和写入指令进行重新排序. 优化常规内存时,这些优化是透明且高效的. 解决由编译器优化或硬件重新排序引起的问题的方法是,在必须从硬件(或其他处理器)角度按特定顺序执行的操作之间设置内存屏障. Linux提供了宏解决方案编译器的执行顺序.

void屏障(void)

此函数通知编译器插入一个内存屏障,但对硬件无效. 编译后的代码会将当前CPU寄存器中的所有修改后的值存储到内存中. 当需要这些数据时,将再次从内存中读取它们.

2. C语言关键字volatile

C语言关键字volatile(请注意,它用于修改变量,而不是上面介绍的__volatile__)表示变量的值可以在外部更改,因此每次都无法将对这些变量的访问缓存在寄存器中-访问时使用. 此关键字通常在多线程环境中使用,因为在编写多线程程序时,同一变量可能会被多个线程修改,并且程序会通过该变量同步每个线程,例如:

DWORD __stdcall threadFunc(LPVOID信号)

{

int * intSignal = reinterpret_cast (信号);

* intSignal = 2;

同时(* intSignal!= 1)

睡眠(1000);

返回0;

汇编语言和c语言_c语言程序设计试题汇编答案_c语言对应汇编语句

}

程启动时将intSignal设置为2,然后循环运行直到intSignal为1退出. 显然,必须从外部更改intSignal的值,否则该线程将不会退出. 但是,即使在外部将其值更改为1,该线程在实际运行时也不会退出,通过查看相应的伪汇编代码可以清楚地看到该线程:

移动斧头,信号

标签:

如果(ax!= 1)

转到标签

对于C编译器,它不知道此值将被其他线程修改. 自然地将其缓存在寄存器中. 记住,C编译器没有线程的概念!这时,您需要使用volatile. volatile的原始含义是: 可以在当前线程之外更改此值. 换句话说,我们需要在threadFunc的intSignal前面添加volatile关键字. 此时,编译器知道该变量的值将在外部更改,因此每次访问该变量时都会再次读取该变量,并且循环变为如下伪代码所示:

标签:

移动斧头,信号

如果(ax!= 1)

转到标签

3,内存

基于上述知识,不难理解内存修改描述符. 内存描述符告诉GCC:

1)不要重新排列内联汇编说明和先前的说明;也就是说,在执行内联汇编代码之前,必须先执行之前的所有指令

2)不要在寄存器中缓存变量,因为此代码可能使用内存变量,并且这些内存变量将以不可预测的方式更改,因此GCC插入必要的代码以首先将变量值缓存在寄存器中到内存,如果以后再访问这些变量,则需要再次访问内存.

如果汇编指令修改了内存,但GCC本身不知道,因为在输出部分中没有描述,则需要在修改描述部分中添加“内存”,以告知GCC内存已被修改, GCC知道了这些信息之后,将在该部分指令之前插入必要的指令,并且针对缓存优化的寄存器中的变量值将首先被写回到内存中,如果这些变量将被再次使用稍后,将再次读取它们.

使用“ volatile”也可以达到这个目的,但是我们在每个变量之前添加此关键字,最好使用“ memory”.

++ / cppjs / 2008331 / 107755.html

我最近开始学习C语言,我想记录一下我在学习过程中的一些经验. 让我将其作为我学习经历的笔记. 如果您不小心阅读了这些文章,则可以帮助我指出一些未正确理解的文章. 在这里,我将非常感谢. 哈哈.

使用两种类型的限定符(volatile和strict)与优化编译器之间存在一定的关系. 在变量类型定义中使用关键字volatile指示该变量的值中存在不确定因素. 换句话说,此变量不仅会被我们编写的程序更改,而且可能会被外部代理程序更改(例如: 硬件中断,外部程序等). 这样,不能保证如果程序不更改此变量的值并且有多次调用,则在寄存器中输入的值将是正确的.

从编译器优化的角度,举一个例子:

int x = 5;

int a,b;

a = x;

b = x;

因为程序没有更改X的值,但是有多个调用,为了优化运行速度,编译器将为a分配一个值,然后将X的值从内存中放入寄存器中. 当给b赋值时,不要再次在X存储器地址中读取该值,而是直接在寄存器中给b赋5. 此优化对于普通变量没有问题. 但是,如果将其定义为volatile int x;这意味着x可以由程序代码之外的其他代理更改. 如果编译器也使用此优化,则很可能在分配b的值时,x的值已被程序外部的硬件中断更改. 这样,从寄存器获得的值肯定是错误的.

因此,在将volatile关键字添加到变量时,除了表明该变量可以被其他代理更改外,还明确指出编译器无法以上述方式优化此变量: 每次调用此变量时,从变量的地址获取值,而不是从寄存器获取值(此变量使用的硬件内存地址是与并行运行的其他程序共享数据,因此,程序本身更改变量值还是其他代理更改变量值,它正在更改内存地址数据. )

看一个有趣的例子:

int平方(volatile int * a)

{

返回(* a * * a);

}

此函数的目的是计算平方根,但是由于指针使用volatile关键字,因此不能保证两次获得的指针地址中的值都相同,因此计算结果可能不是我们需要. 考虑更改为:

int平方(volatile int * a)

{

汇编语言和c语言_c语言对应汇编语句_c语言程序设计试题汇编答案

int temp = * a;

返回(temp * temp);

}

restrict关键字只能用于修改指针,指示定义的指针是访问指针中数据的唯一方法. 目的是告诉编译器可以进行一些优化. 查看示例:

int x = 2;

int * a =(int *)malloc(sizeof(int));

* a = 2;

int * b =&x;

* a + = 2;

* b + = 2;

x * = 3;

* a + = 3;

* b + = 3;

在编译器进行优化时,可以使用以下语句代替: * a + = 5;这对于a是正确的,但是如果使用* b + = 5来优化b,那是不正确的. 因为其他变量会影响结果. 因此,当编译器不确定某些因素时,它将放弃寻找优化的方法. 如果在变量前添加strict关键字. 它告诉编译器放心地进行优化. 但是编译器不会验证您定义为strict的指针是否确实是访问某些数据的唯一方法. 就像数组的下标超出范围一样,如果不遵循规则,编译器将不会指出错误,但后果是您自己负责的: )

还要看一个有趣的类别:

voidchange_array(限制int *数组,const限制int *值,const int大小)

{

for(int i = 0; i

{

数组[i] + = *值;

}

}

int主(无效)

{

int *数组[SIZE] = {1,2,3};

change_array(数组和数组[0]c语言对应汇编语句,大小);

for(int i = 0; i

{

printf(“%d \ n”,数组[i]);

}

}

如果编译器支持优化,则运行后的结果为: 2 3 4而不是实际的正确结果: 2 4 5.这表明在定义函数时两个指针受到限制,因此编译器对其进行了优化: 当程序调用该函数时,将在寄存器中生成值指针的变量值的副本. 随后的执行是获取寄存器的值. 同时,可以看出,当您不遵循由strict定义的指针定义的规则并且只能由指针修改(函数中值指针所指向的数据时,数组指针也是在调用main时进行了修改),编译器不会进行检查.

为了进行优化,必须强制使用volatile,并建议使用strict. 也就是说,添加volatile不会强制优化,而添加strict编译器可能不一定会优化. 在大多数情况下,限制的结果与不添加任何内容的结果相同. Restrict仅告诉编译器可以自由进行一些优化假设. 它还告诉调用方仅使用满足limit中定义的条件的参数,如果不听话,呵呵. .

关键字strict是由C99标准添加的,在C ++中不受支持,因此我无法通过在VC ++中添加restrict关键字来进行编译: (

关于加入限制,我还在网上找到了一个短篇小说:

为了提高Cray机器的效率,ANSI C委员会提出了一种名为noalias的机制来解决此问题. 它用于解释可以将C指针视为别名,但是这种机制还不成熟. 此事件激怒了Dennis Ritchie,他对C的标准化过程进行了唯一的干预. 他写了一封公开信,说“ noalias必须站在一边,这是不能商量的. ”

后来,Cray的Mike Holly发现了这个问题,并向数值C语言扩展工作组和C ++委员会提出了改进的抗锯齿建议. 建议的想法是允许程序员通过将指针描述为“限制”来声明可以将其视为没有别名. 该提议已被C99接受,但标准的C ++拒绝了.


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-222589-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...