Ваша основная проблема в том, что вы не выделили память для своей строки. В C вы несете ответственность за все управление памятью. Если вы объявляете переменные в стеке, это легко. С указателями это немного сложнее. Поскольку у вас есть строка char* str = NULL
, когда вы пытаетесь ввести в нее scanf
, вы записываете байты в NULL
, что недопустимо. Спецификатор %s
записывает то, на что указывает str
; он не может изменить str
, так как параметры передаются по значению. Вот почему вы должны передать &acct
вместо acct
.
Так как вы это исправите? Вы должны предоставить память, где может жить читаемая строка. Что-то вроде char str[5] = ""
. Это делает str
массивом из пяти элементов, достаточно большим, чтобы содержать «выход» и его завершающий нулевой байт. (Массивы распадаются на указатели при малейшей провокации, поэтому у нас все в порядке.) Однако это опасно. Если пользователь вводит строку malicious
, вы собираетесь записать "malic"
в str
и байты для "icious\0"
во все, что идет после этого в памяти. Это переполнение буфера, и это классическая ошибка. Самый простой способ исправить это - потребовать от пользователя ввести команду, состоящую не более чем из N букв, где N - самая длинная команда, которую вы имеете; в этом случае N = 4. Затем вы можете указать scanf
, что нужно прочитать не более четырех символов: scanf("%4s %u %i", cmd, &acct, &amt)
. %4s
гласит «читать максимум четыре символа», поэтому вы не можете испортить другую память. Однако обратите внимание, что если пользователь введет malformed 3 4
, вы не сможете найти 3 и 4, так как вы будете смотреть на ormed
.
Причина, по которой вы можете сделать scanf("%s %u %i", &cmd, &acct, &amount)
, заключается в том, что C не является типобезопасным. Когда вы дали ему &cmd
, вы дали ему char**
; тем не менее, он был счастлив рассматривать это как char*
. Таким образом, он записал байты сверх cmd
, поэтому, если вы передали строку exit
, cmd
может (если бы она была шириной в четыре байта и имела соответствующий порядковый номер) быть равной 0x65786974
(0x65 = e
, 0x78 = x
, 0x69 = i
, 0x74 = t
). И затем нулевой байт или любые другие байты, которые вы передали, вы начинаете записывать поверх случайной памяти. Если вы также измените его на strcmp
, то также будет обрабатывать значение из str
как строку, и все будет согласованно. Что касается того, почему return 0;
терпит неудачу, но exit(0)
работает, я не уверен, но у меня есть предположение: вы, возможно, писали по обратному адресу main
. Он также хранится в стеке, и если он окажется после cmd
в макете стека, вы можете обнулить его или записать его. Теперь exit
должен выполнить очистку вручную, перепрыгивая в нужные места и т. Д. Однако, если (как я думаю, дело обстоит, хотя я не уверен), main
ведет себя как любая другая функция, ее return
переходит на место в стеке, хранящемся в качестве адреса возврата (что, вероятно, является какой-то процедурой очистки). Однако, так как вы набросались на это, вы получаете прерывание.
Теперь есть пара небольших улучшений, которые вы можете сделать. Во-первых, поскольку вы рассматриваете done
как логическое значение, вы должны зациклить while (!done) { ... }
. Во-вторых, текущая настройка требует, чтобы вы написали exit 1 1
для выхода из программы, хотя бит 1 1
не должен быть необходим. В-третьих, вы должны проверить, успешно ли вы прочитали все три аргумента, чтобы не было ошибок / несоответствий; например, если вы не исправите это, то введите
deb 1 2
deb 3 a
Вызывает debit(1,2)
и debit(3,2)
, оставляя при этом a
на входе, чтобы сбить вас с толку. Наконец, вы должны выйти из EOF аккуратно, а не зацикливаться на вечности, выполняя последнее, что вы сделали. Если мы соберем это вместе, мы получим следующий код:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void credit(unsigned int acct, int amount);
void debit(unsigned int acct, int amount);
void service_fee(unsigned int acct, int amount);
int main() {
char cmd[5] = "";
unsigned int acct = 0;
int amount = 0;
int done = 0;
while (!done) {
if (feof(stdin)) {
done = 1;
} else {
if (scanf("%4s", cmd, &acct) != 1) {
fprintf(stderr, "Could not read the command!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if (strcmp(cmd, "exit") == 0) {
done = 1;
} else {
if (scanf(" %u %i", &acct, &amount) != 2) {
fprintf(stderr, "Could not read the arguments!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if ((strcmp(cmd, "dep") == 0) || (strcmp(cmd, "deb") == 0))
debit(acct, amount);
else if ((strcmp(cmd, "wd") == 0) || (strcmp(cmd, "cred") == 0))
credit(acct, amount);
else if (strcmp(cmd, "fee") == 0)
service_fee(acct, amount);
else
fprintf(stderr, "Invalid input!\n");
}
}
/* Cleanup code ... */
}
return 0;
}
/* Dummy function bodies */
void credit(unsigned int acct, int amount) {
printf("credit(%u, %d)\n", acct, amount);
}
void debit(unsigned int acct, int amount) {
printf("debit(%u, %d)\n", acct, amount);
}
void service_fee(unsigned int acct, int amount) {
printf("service_fee(%u, %d)\n", acct, amount);
}
Обратите внимание, что если не существует «кода очистки», вы можете заменить все ваши варианты использования done
на break
и удалить объявление done
, что даст более приятный цикл
while (1) {
if (feof(stdin)) break;
if (scanf("%4s", cmd, &acct) != 1) {
fprintf(stderr, "Could not read the command!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if (strcmp(cmd, "exit") == 0) break;
if (scanf(" %u %i", &acct, &amount) != 2) {
fprintf(stderr, "Could not read the arguments!\n");
scanf(" %*s "); /* Get rid of the rest of the line */
continue;
}
if ((strcmp(cmd, "dep") == 0) || (strcmp(cmd, "deb") == 0))
debit(acct, amount);
else if ((strcmp(cmd, "wd") == 0) || (strcmp(cmd, "cred") == 0))
credit(acct, amount);
else if (strcmp(cmd, "fee") == 0)
service_fee(acct, amount);
else
fprintf(stderr, "Invalid input!\n");
}