Функция get () с использованием getchar из сборки - PullRequest
0 голосов
/ 02 сентября 2018

У меня возникли проблемы с созданием функции gets() для кода C, который я выполняю для одного из моих классов. Так что у меня уже есть функция 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 (), чтобы я мог правильно отобразить строку ввода?

Например, если я вхожу в "привет мир", выводится только "l ol"

1 Ответ

0 голосов
/ 02 сентября 2018

Ваша реализация 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 и бессмысленного окончания строки, но, возможно, более полезны для изучения.

...