【问题标题】:What is the difference between these two forms of inline assembler in C?C中这两种形式的内联汇编程序有什么区别?
【发布时间】:2011-01-06 10:02:34
【问题描述】:

背景:我的任务是为Unitech HT630 编写一个数据收集程序,它运行一个专有的 DOS 操作系统,可以运行为 16 位 MS DOS 编译的可执行文件,尽管有一些限制。我正在使用 Digital Mars C/C++ 编译器,它似乎工作得很好。

对于某些事情,我可以使用标准 C 库,但其他事情,例如在单元的屏幕上绘图,则需要汇编代码。设备文档中给出的汇编示例与我被教导在 C/C++ 中使用内联汇编代码的方式不同。作为参考,以下示例中的BYTE 的类型为unsigned char

我得到的示例代码示例:

#include <dos.h>

/* Set the state of a pixel */
void LCD_setpixel(BYTE x, BYTE y, BYTE status) {
  if(status > 1 || x > 63 || y > 127) {
    /* out of range, return */
    return;
  }
  /* good data, set the pixel */
  union REGS regs;
  regs.h.ah = 0x41;
  regs.h.al = status;
  regs.h.dh = x;
  regs.h.dl = y;
  int86(0x10, &regs, &regs);
}

我总是被教导如何使用内联汇编:

/* Set the state of a pixel */
void LCD_setpixel(BYTE x, BYTE y, BYTE status) {
  if(status > 1 || x > 63 || y > 127) {
    /* out of range, return */
    return;
  }
  /* good data, set the pixel */
  asm {
    mov AH, 41H
    mov AL, status
    mov DH, x
    mov DL, y
    int 10H
  }
}

这两种形式似乎都有效,到目前为止,我还没有遇到任何一种方法的问题。对于 DOS 编程,一种形式是否比另一种形式更好? int86 函数是否为我处理了第二个示例中我没有在自己的汇编代码中处理的内容?

提前感谢您的帮助。

【问题讨论】:

  • 标题具有误导性/错误。第一个版本没有内联汇编,只是系统调用的函数调用包装器。 (或者编译器可以将 int86() 实现为内在并内联正确的 asm)。

标签: c assembly dos inline-assembly


【解决方案1】:

那不是内联汇编,它是 C。非常低级的 C,使用函数引起中断,但仍然是 C。

This page 有一些文档(对于 DJGPP 编译器,您的可能工作方式不同),包括用于表示寄存器的结构。它还指出:

请注意,与 __dpmi_int 函数,通过的请求 int86 和类似的功能是 经过特殊加工制成 适合调用实模式 保护模式的中断 程式。例如,如果一个特定的 例程接受 BX 中的一个指针,int86 期望你放一个(保护模式) EBX 中的指针。因此,int86 应该对每个人都有具体的支持 你调用这个的中断和函数 大大地。目前,它只支持一个 所有可用中断的子集和 功能 [...]

【讨论】:

  • 这对于 DJGPP C 编译器是正确的,因为 int86 不是一个标准函数,它并没有考虑到 Digital Mars 编译器以相同的方式工作,它可能或多或少有限制
【解决方案2】:

第一种形式更具可读性,这也很重要;-)

如果您想知道 int86 是否在背后做某事,只需编译您的程序并检查生成的汇编代码

【讨论】:

  • 我认为哪种形式比另一种更“可读”是个人意见的问题,因为我个人觉得第二种更具可读性,但两者都可以理解。剖析编译的代码将是最后的手段,如果有人在这里没有我的答案,那么我肯定会走那条路。
  • 第一种形式对于使用不同格式进行内联汇编的编译器来说更易读。但是您可能会担心不必要的函数调用的开销,加上寻址 REGS 联合的元素,然后大概是 int86 复制所有寄存器(所有在联合中定义的寄存器,而不仅仅是您使用的那些)内存,然后将它们全部复制回来(然后您将其丢弃)。
  • @P-Nuts - 我还在喝咖啡,我认为 Alon 的意思是可读的,就像人眼可读的一样。我同意第一种形式可以被更多的编译器读取。
【解决方案3】:

通过调用 int86,您的代码保留在 C 中。无论哪种方式,它都是通过执行系统中断来写入像素。

如果您有 很多 像素要写入,并且您开始严重遇到速度问题,则可能有一种更直接(安全性更低但可能值得)的方式直接写入像素记忆。

【讨论】:

  • 可能会有很多像素要写,因为客户端在应用程序开始时正在讨论启动画面。现在,我只需要它在屏幕上绘制奇数线。到时候这个问题可能会成为它自己的问题。
  • @Heather:是的,我查了一下你的机器,屏幕是单色的,而且很小,所以我怀疑图形是否会成为瓶颈。
【解决方案4】:

当您使用 int86 函数调用时,这是一个 C 运行时库调用,它设置寄存器并发出 DOS interrupt 函数。这两种方法实际上是相同的,但有一个例外,当您使用内联汇编程序时,代码实际上是在编译和链接时嵌入到目标代码中。

内联汇编会被认为更快,因为您不需要调用 C 运行时库来为您调用 DOS 中断所涉及的开销。您有责任确保在使用内联汇编时有足够的堆栈空间,而 C 运行时库会在调用 int86 函数之前设置寄存器时负责分配堆栈空间。

int86 是一种更容易调用 DOS 中断的方法。这在旧的 Borland Turbo C 编译器套件和 Microsoft 中非常流行,我说的是 Win 3.1 出现之前的旧编译器。

说到中断 0x10,它负责视频输出,如果我没记错的话,当时,一些 BIOS 破坏了bp 寄存器,解决方法是这样做:

__asm{
   push bp;
}
/* set up the registers */
int86(0x10, &regs, &regs);
__asm{
   pop bp;
}

您可以在 Ralph Brown 的中断列表here 中找到广泛的 BIOS 功能。 HelpPC v2.1 也可能有帮助,发现 here

【讨论】:

  • 现在回想起来,我想我有一份 Ralph Brown 在大学时的中断清单,但我不知道我用它做了什么。好像我忘记了这个很棒的资源存在,谢谢!这很有帮助。
【解决方案5】:

两个代码 sn-ps 完成相同的事情。第一个的最大优势是,当您切换编译器时,您仍有可能使用它。并且您不要踩在“C”编译器的代码生成器用于其他目的的寄存器上。你肯定会忘记在你的 asm sn-p 中处理的事情。

【讨论】:

  • 我怀疑我没有在 asm 块中做我需要做的事情。谢谢。
【解决方案6】:

您应该查看编译器手册,找出谁负责在内联汇编部分之后恢复寄存器值。由于您的变量已分配给寄存器,因此值的意外更改可能会导致难以发现的错误。 int86(0x10, &regs, &regs); 保存寄存器并在执行软件中断后恢复它们。

一些编译器接受指令来定义一个clobber列表(应该保存和恢复的寄存器)。通常,汇编程序部分应该保存将通过 push 更改的寄存器和标志,并由编译器或您自己使用 pop 恢复它们。因此应该首选第一个例子。

【讨论】:

  • 我同意 int86 - 我认为我会使用这种方法来确保安全,除非速度成为一个真正的问题,谢谢 :)
猜你喜欢
  • 2019-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-08
相关资源
最近更新 更多