Ваша реализация C gets
не работает , независимо от реализации asm 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 и clang для 32-битного x86 на Godbolt , должно быть очень похоже на x86-16.)
Вы также можете / вместо этого проверить '\n'
в зависимости от вашей реализации getchar и от того, нормализует ли она окончания строк или нет. И помните, что остановка после чтения \r
оставит \n
непрочитанным, если у вас есть DOS "\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.
Это закроет память над обратным адресом. char getchar(void)
не принимает никаких аргументов, поэтому ваша функция не «владеет» этой памятью. Ваш компилятор должен ожидать возвращаемого значения в AL. (И если вы подумали, что он возвращается по ссылке, нет, вы просто перезаписываете указатель arg. За исключением того, что вызывающий абонент даже не передает его.)
Если вы хотите, чтобы ваш getchar
мог возвращать EOF, отличный от байта 0xFF
, объявите его как возвращающий int
и ноль AH после выполнения системного вызова. (Таким образом, вы можете вернуть 16-битный -1
в AX или ноль-расширенный unsigned char
в AX (т.е. значение в AL).
Кстати, есть причина gets()
устарела , и фактически удалена в ISO C11 : невозможно предотвратить переполнение буфера при чтении с неизвестной длиной вход.
Ваша функция должна принимать ограничение размера в качестве второго аргумента.
Программирование Arduino AVR или ARM CPU напрямую , вероятно, будет проще для изучения и более полезным, чем использование системных вызовов DOS на эмулируемом 8086. Если вы собираетесь это сделать, нет смысла делать это на реальном оборудовании против симулятора.
Изучение x86 как вашего первого языка ассемблера - это нормально, если вы не возитесь с сегментацией и не пытаетесь написать загрузчик (есть много таинственных устаревших вещей с гейтом A20 и переключением с реального режима на защищенный режим). Системные вызовы DOS полностью устарели, за исключением поддержки устаревших кодовых баз. Изучить детали того, как отличается AH = ?? / int 21h
Системные вызовы работают точно так же, как и COBOL. BIOS int 10h
и другие семейства немного более полезны, если вы создаете устаревший загрузочный сектор (вместо EFI), но вам не нужно делать это для изучения asm. Если вы изучите asm в пользовательском пространстве под Linux, Windows, Mac, * BSD или чем-то еще, вам станет легче понять / изучить другие способы общения с внешним миром позже, если вам когда-либо понадобится, и узнать, как работают ядра.
Системные вызовы Linux имеют аналогичный ABI (eax=call number
/ int 0x80
, sysenter
или syscall
), но системные вызовы Linux - это более или менее системные вызовы POSIX, о которых полезно знать для реального мир низкоуровневого программирования.
Сложности ввода с буферизацией строки POSIX TTY с sys_read
отличаются от сложностей функций чтения символов DOS и бессмысленного окончания строки, но, возможно, более полезны для изучения.