包含内联C代码的程序集如何工作?

时间:2014-05-23 14:30:55

标签: c assembly embedded

我见过Arduino和其他硬件的代码,这些代码具有与C内联的汇编,这些代码如下:

asm("movl %ecx %eax"); /* moves the contents of ecx to eax */
__asm__("movb %bh (%eax)"); /*moves the byte from bh to the memory pointed by eax */

这实际上是如何工作?我意识到每个编译器都是不同的,但是这样做的常见原因是什么,以及有人可以利用这个?

4 个答案:

答案 0 :(得分:10)

内联汇编程序代码直接进入完整的汇编代码,并且是一体的。当您真正需要完全控制指令序列时,或者当您无法让优化程序使用您的代码时,就可以执行此操作。也许你需要每一个时钟滴答。也许您需要代码的每个分支都采用完全相同的时钟周期数,并使用NOP来实现这一点。

无论如何,有很多理由可能会有人想要这样做,但你真的需要知道你在做什么。这些代码块对你的编译器来说是非常不透明的,如果你做的不好,你可能不会得到任何警告。

答案 1 :(得分:6)

通常,编译器只会将汇编程序指令插入其生成的汇编程序输出中。它会做到这一点而不考虑后果。

例如,在此代码中,优化器正在执行复制传播,从而看到y = x,然后z = y。因此它用z = x替换z = y,希望这将允许它执行进一步的优化。但是,它并没有发现我在平均时间内弄乱了x的值。

char x=6;
char y,z;

y=x;                 // y becomes 6

_asm                    
    rrncf x, 1       // x becomes 3. Optimiser doesn't see this happen!
_endasm  

z=y;                 // z should become 6, but actually gets
                     // the value of x, which is 3

为了解决这个问题,您实际上可以告诉优化器不要对此变量执行此优化。

volatile char x=6;   // Tell the compiler that this variable could change
                     // all by itself, and any time, and therefore don't
                     // optimise with it.
char y,z;

y=x;                 // y becomes 6

_asm                    
    rrncf x, 1       // x becomes 3. Optimiser doesn't see this happen!
_endasm  

z=y;                 // z correctly gets the value of y, which is 6

答案 2 :(得分:5)

历史上,C编译器生成汇编代码,然后由汇编程序将其转换为机器代码。内联汇编作为一个简单的特性出现 - 在中间汇编代码中,在那时,注入一些用户选择的代码。有些编译器会直接生成机器代码,在这种情况下,它们包含汇编程序或调用外部汇编程序来生成内联汇编代码段的机器代码。

汇编代码最常见的用法是使用编译器无法生成的专用处理器指令。例如,禁用关键部分的中断,控制处理器功能(缓存,MMU,MPU,电源管理,查询CPU功能......),访问协处理器和硬件外围设备(例如inb / outb指令x86)等你很少找到asm("movl %ecx %eax"),因为这会影响它周围的C代码也在使用的通用寄存器,但像asm("mcr p15, 0, 0, c7, c10, 5")这样的东西有它的用途(ARM上的数据存储障碍) )。 OSDev wiki有几个带代码段的示例。

汇编代码对于实现破坏C流控制模型的功能也很有用。一个常见的例子是线程之间的上下文切换(无论是协作还是抢占,无论是否在相同的地址空间中),需要汇编代码来保存和恢复寄存器值。

汇编代码对于手动优化代码的小内存或速度也很有用。随着编译器变得更加智能化,现在在应用程序层面上很少有相关性,但它在大多数嵌入式领域仍然具有相关性。

有两种方法可以将装配与C组合:使用内联装配,或通过将装配模块与C模块链接。链接可以说更清晰但并不总是适用:有时你需要在函数中间使用一条指令(例如,在上下文切换中保存寄存器,函数调用会破坏寄存器),或者你不想支付成本函数调用。

大多数C编译器都支持内联汇编,但语法各不相同。它通常由关键字asm_asm__asm__asm__引入。除了汇编代码本身之外,内联汇编构造还可以包含允许您在汇编和C之间传递值的其他代码(例如,请求将局部变量的值复制到条目上的寄存器),或者声明汇编代码破坏或保留某些寄存器。

答案 3 :(得分:3)

asm(“”) __ asm __ 都是有效用法。基本上,如果关键字 asm 与程序中的某些内容冲突,则可以使用 __ asm __ 。如果您有多个指令,则可以在双引号中每行写一个,并在指令后加'\ n''\ t'。这是因为gcc将每条指令作为字符串发送到(GAS),并且通过使用换行符/选项卡,您可以将正确格式化的行发送到汇编程序。您问题中的代码段是基本内联

基本内联汇编中,只有说明。在扩展程序集中,您还可以指定操作数。它允许您指定输入寄存器,输出寄存器和破坏寄存器列表。指定要使用的寄存器不是强制性的,您可以将其保留给GCC,这可能更适合GCC的优化方案。扩展asm的一个例子是:

__asm__ ("movl %eax, %ebx\n\t"
           "movl $56, %esi\n\t"
           "movl %ecx, $label(%edx,%ebx,$4)\n\t"
           "movb %ah, (%ebx)");

请注意,除了最后一行之外,每行末尾的'\ n \ t',每行都用引号括起来。这是因为gcc将每个作为指令发送到我之前提到的字符串。需要换行/标签组合,以便按照正确的格式输入行。