您的基本问题是您没有为字符串分配内存。在 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*。因此,它写了字节over cmd,所以如果你传入字符串exit,cmd 可能(如果它是四个字节宽并且具有适当的字节序)等于@987654347 @ (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");
}