我在为我的一个类做的C代码上制作gets()
函数时遇到了一些问题。所以我已经有一个getchar()
函数,但在汇编,我用C调用它来使用extern
事情是,目前我正在运行代码我输入一个字符串,它不显示完整的字符串而不是一些字符。
这是我的代码: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 gets
实现如何,您的C getchar
实现都会被破坏。您可以使用桌面上的普通调试器在普通的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
}
返回字符串长度而不是将其丢弃在知道它的函数中总是有用的,这只会给编译器带来一些额外的指令。指针增量简化了寻址模式,节省了代码大小。
(与gcc and clang for 32-bit x86 on Godbolt很好地编译,对于x86-16应该非常相似。)
你也可以/而不是检查'\n'
取决于你的getchar实现,以及它是否规范化行结尾。并记住,如果你有DOS \r
线结尾,在阅读\n
后停止会留下一个"\r\n"
未读。
在ISO C中,getchar()
应该只为你在文本模式下打开的文件提供'\n'
行结尾,但你已经使getchar
成为DOS int 21h
/ AH=1(从STANDARD INPUT,WITH ECHO读取字符)功能的包装器。这就是设置实现行为的原因。
# in _getchar:
mov [bp + 4], al ; clobber memory you don't own.
那会破坏返回地址之上的内存。 char getchar(void)
不接受任何args,所以你的函数不“拥有”那个记忆。您的编译器应该期望AL中的返回值。 (如果你认为那是通过引用返回的,不,你只是覆盖指针arg。除了调用者甚至没有传递一个。)
如果你希望你的getchar
能够返回与0xFF
字节不同的EOF,请将其声明为返回int
,并在进行系统调用后将其归零。 (所以你可以在AX中返回一个16位的-1
,或者在AX中返回一个零扩展的unsigned char
(即AL中的值)。
顺便说一下,有一个原因gets()
is deprecated,并且实际上已经在ISO C11中删除了:当读取未知长度输入时,不可能防止缓冲区溢出。
你的函数应该作为第二个arg的大小限制。
直接编程Arduino的AVR或ARM CPU可能比在模拟的8086上使用DOS系统调用更容易学习,也更有用。如果你打算这样做,那么在真正的硬件上做这件事就没有意义了。模拟器。
学习x86作为你的第一个汇编语言是好的,如果你不搞乱分段,你不会尝试编写一个bootloader(A20门有很多古怪的遗留东西,从真实模式切换到保护模式) 。除了维护遗留代码库之外,DOS系统调用完全过时了。学习不同AH的细节如何? / int 21h
系统调用工作完全和COBOL一样有用。如果您正在制作传统启动扇区(而不是EFI),那么BIOS int 10h
和其他系列会稍微有用,但您不需要这样做来学习asm。如果您在Linux,Windows,Mac,* BSD或其他任何领域的用户空间中学习asm,那么以后可以很容易地理解/学习与外部世界通信的其他方式(如果您需要),并了解内核的工作方式。
Linux系统调用具有类似的ABI(eax=call number
/ int 0x80
,sysenter
或syscall
),但Linux系统调用或多或少是POSIX系统调用,它对于了解真实的低级编程很有用。
POSIX TTY行缓冲输入与sys_read
的复杂性不同于DOS字符读取功能和行尾废话的复杂性,但可以说更有用。