gets() 函数使用来自程序集的 getchar

gets() function using getchar from assembly

我在为我的 类 之一编写的 C 代码上创建 gets() 函数时遇到了一些问题。所以我已经有一个 getchar() 函数但是在汇编时我使用 extern 从 C 调用它 事情是现在我是 运行 我输入一个字符串的代码并且它不显示完整的字符串而是一些字符。

这是我的 atm 密码: C代码:

extern char getchar(void);
extern void putchar(char data);
void gets(char *str);
void puts(char *str);
void new_line();

char string[20];

int main(){
    while(1){
        gets(string);
        new_line();
        puts(string);
    }
    return 0;
}

void new_line(){
    putchar(0xD);
    putchar(0xA);
}
void gets(char *str){
    unsigned char i = 0;
    while((*str = getchar()) != 0xD){
        str[i] = getchar();
        i++;
    }
}

void puts(char *str){
    while(*str){
        putchar(*str++);
    }
}

和我的 ASM 代码以防万一:

.MODEL tiny

.CODE
    public _putchar
    public _getchar

    _putchar    PROC
                push bp
                mov bp, sp
                mov dl, [bp + 4]
                mov ah, 2
                int 21h
                pop bp
                ret
    _putchar    ENDP

    _getchar    PROC
                push bp
                mov bp, sp
                mov ah, 1
                int 21h
                mov [bp + 4], al
                pop bp
                ret
    _getchar    ENDP
END

我是 运行 Arduino Mega 上的代码,使用 MTTTY 和我们老师提供的 8086 解释器。

有什么办法可以用 gets() 函数解决这个问题,以便我可以正确显示输入字符串?

例如,如果我输入 "hello world" 它只会打印 "l ol"

无论 asm getchar 实现如何,您的 C gets 实现都已损坏。您可以在桌面上使用普通调试器在普通 C 实现上调试它。

您调用 getchar() 两次,并且只保存每第二次结果。

第一个结果分配给 str[0] 并检查 '\r'

// your version with comments
void gets_original_buggy (char *str){
    unsigned char i = 0;   // this is an index; it should be an `int` or `size_t`

    while((*str = getchar()) != 0xD){  // overwrite the first byte of the string with an input
        str[i] = getchar();    // get ANOTHER new input and save it to the end.
        i++;
    }
    // str[i] = 0;  // missing zero terminator.
}

我是这样写的:

#include <stddef.h>
//#include <stdio.h>

extern unsigned char getchar(void);

// returns length.
// negative means EOF.  TODO: implement an EOF check if your getchar() supports it.
// FIXME: take a max-length arg to make it possible to prevent buffer overflows.
ptrdiff_t gets(char *str) {
    char *start = str;  // optional

    char tmp;  // read chars into a local, and check before assigning anything to *str
    while( (tmp = getchar()) != '\r') {
        // TODO: also check for EOF
        *str++ = tmp;            // classic pointer post-increment idiom
    }
    *str = 0;     // terminate the C string.

    return str - start;  // optional, return the length
}

return 字符串长度总是有用的,而不是将其丢弃在知道它的函数中,这只会使编译器花费一些额外的指令。指针递增简化了寻址方式,节省了代码量。

(使用 gcc and clang for 32-bit x86 on Godbolt 编译得很好,对于 x86-16 应该非常相似。)

您可能 also/instead 检查 '\n' 取决于您的 getchar 实现,以及它是否规范化行尾。请记住,如果您有 DOS "\r\n" 行结尾,则在阅读 \r 后停止将留下 \n 未读。

在 ISO C 中,getchar() 应该只为以文本模式打开的文件提供 '\n' 行结尾,但你已经 getchar 只是 DOS 的包装器 int 21h / AH=1(从标准输入中读取字符,带回声)功能。这就是设置您的实施行为的原因。

asm 错误:

# in _getchar:
    mov [bp + 4], al         ; clobber memory you don't own.

这会破坏 return 地址以上的内存。 char getchar(void) 不接受任何参数,因此您的函数不会 "own" 那个内存。您的编译器应该期望 AL 中有一个 return 值。 (如果你认为那是 return 引用,不,你只是覆盖了指针 arg。除了调用者甚至没有传递一个。)

如果您希望 getchar 能够 return EOF 不同于 0xFF 字节,请将其声明为 returning int,并且进行系统调用后 AH 为零。 (因此您可以 return AX 中的 16 位 -1,或 AX 中的零扩展 unsigned char(即 AL 中的值)。


顺便说一句,这是有原因的 gets() is deprecated,实际上 在 ISO C11 中删除了:当正在读取未知长度的输入。

您的函数应将大小限制作为第二个参数。


直接对 Arduino 的 AVR 或 ARM CPU 编程可能比在模拟 8086 上使用 DOS 系统调用更容易学习,也更有用。如果你我要这样做,在真实硬件上与模拟器上这样做没有意义。

学习 x86 作为你的第一门汇编语言是可以的,如果你不乱用分段,并且你不尝试编写引导加载程序(A20 门有很多神秘的遗留东西,并且从真正的切换模式到保护模式)。 DOS 系统调用已完全过时,除了维护遗留代码库。学习的细节怎么不一样啊=?? / int 21h 系统调用的工作原理与 COBOL 一样有用。如果您正在制作遗留引导扇区(而不是 EFI),BIOS int 10h 和其他系列会稍微有用一些,但您不需要这样做来学习 asm。如果您在 Linux、Windows、Mac、*BSD 或其他任何目录下的 user-space 中学习 asm,那么理解/学习其他与稍后,如果您需要,了解内核的工作原理。

Linux 系统调用具有相似的 ABI(eax=call number / int 0x80sysentersyscall),但 Linux 系统调用或多或少是 POSIX 系统调用,了解这些调用对于现实世界的低级编程很有用。

POSIX TTY 行缓冲输入 sys_read 的复杂性不同于 DOS 字符读取功能和行结束废话的复杂性,但可以说更有用学习。