Не работает функция scanf
Когда я запускаю этот код на FASM, то сначала выводится "What is your name?". Потом я ввожу имя, нажимаю enter, программа зависает на несколько секунд и вылетает когда выполняется scanf.
format pe console
include 'C:\Program Files\FASM\INCLUDE\WIN32A.INC'
entry start
section '.code' code readable executable
start:
push question
call [printf]
push name
push formatName
call [scanf]
push name
push answer
call [printf]
call [getch]
push NULL
call[ExitProcess]
section '.data' data readable
question db 'What is your name? ', 0
answer db 'Hello, %s! ', 0
formatName db '%s', 0
NULL = 0
name rd 2
section '.idata' import data readable writeable
library kernel, 'kernel32.dll',\
msvcrt, 'msvcrt.dll'
import kernel,\
ExitProcess, 'ExitProcess'
import msvcrt,\
printf, 'printf',\
getch, '_getch',\
scanf, 'scanf_s'
Я попробовал использовать scanf в c++ в этом коде:
#include <iostream>
int main()
{
std::string Name;
std::cout << "What is your name?\n";
scanf_s("%s", &Name);
std::cout << "Hello, " << Name << "!";
}
Результат тот же самый. Я думаю, что дело всё в msvcrt.dll. Кто-нибудь знает, как исправить scanf?
Ответы (2 шт):
char s[10];
scanf_s("%9s", s, (unsigned)_countof(s)); // buffer size is 10, width specification is 9
И на ассемблере тоже надо уточнять сколько памяти вы выделили под name?
Ошибка возникает из-за того, что функции msvcrt.dll используют соглашение о вызовах "cdecl", при котором за очистку аргументов в стеке отвечаешь ты сам. Например положили мы в стек 2-аргумента для printf(), то после вызова этого printf() нужно добавить [add esp,2*4]. Если положили 3-аргумента, то соответственно [add esp,3*4]. Иначе стек остаётся не сбалансированным и затирается адрес-возврата. Вообще-то, именно для отлова таких багов придумали отладчик.
У системных-же библиотек типа Kernel32.dll соглашение "stdcall", и их функции сами очищают стек от своих аргументов, при помощи пролога на входе [mov ebp,esp], и эпилога на выходе [mov esp,ebp]. Есть ещё тип вызовов "fastcall", который используют 64-битные приложения - здесь аргументы передаются не через стек, а через регистры.
Чтобы соблюдать подобные типы соглашений, FASM имеет для каждого из них свои макросы - для вызовов "cdecl" (c-declaration) предусмотрен макрос "cinvoke", а fn. "stdcall" вызываются при помощи обычного "invoke" (без приставки "с"). Более того имеется и инклуд "win32ax.inc", который позволяет использовать укороченные директивы типа .code, вместо портянки "section .code code readable executable" и прочих. И спецификаторы с текстом можно помещать сразу в аргументы (как это делается в си), обрамляя их в угловые скобки - вот пример:
format pe console
include 'win32ax.inc'
entry start
;//------------
.data
buff db 0 ;//<--- буфер в 4К до конца секции-данных
;//------------
.code
start: cinvoke printf,<10,' What is your name? ', 0>
cinvoke scanf,<'%s',0>,buff
cinvoke printf,<10,' Hello, %s!',0>,buff
cinvoke getch ;//<--- вызов "CDECL" = cinvoke
invoke ExitProcess,0 ;//<--- вызов "StdCall" = invoke
;//------------
section '.idata' import data readable
library kernel32,'kernel32.dll',msvcrt,'msvcrt.dll'
import msvcrt,printf,'printf',getch,'_getch',scanf,'scanf'
include 'api\kernel32.inc'