C语言fgets存储用户名失败问题排查及修正请求
问题描述
使用C语言编写的代码在通过fgets获取用户名并存储到文件时出现以下异常:
- 文件中仅显示空的
Username行和Password行 - 输入操作时会出现多余空行
怀疑问题与fgets函数、clearBuffer函数有关,临近提交截止日期,需要指出代码错误、提供修正后的代码并解释问题原因。
错误分析
scanf读取输入后未清理缓冲区,导致后续fgets直接读取残留的换行符,获取空内容fopen的文件名错误使用了shell命令格式"touch filename.txt",无法正确打开/创建目标文件filename.txtclearBuffer函数调用时机错误:在fgets之后调用会导致程序等待额外输入,引发多余空行strtok的使用存在安全风险:若fgets读取空字符串,strtok返回NULL会导致后续操作异常check_database为void类型函数,却使用return 1;错误返回值daftar_akun_database中未检查fopen的返回值,文件打开失败时会触发崩溃- 递归调用
check_database和daftar_akun_database存在栈溢出风险
修正后的代码
#include <stdio.h> #include <string.h> #include <stdbool.h> // 声明未定义的函数 void pilihdaerahutama(); void tampilan_awal() { int angka_daerah; do { printf("Daerah\n.....................................\n1. Daerah\n"); printf("Ketik angka 1 jika ingin memilih daerah yang dituju dan angka 0 jika tidak ingin melanjutkan!\n"); scanf("%i", &angka_daerah); // 清理scanf后的缓冲区 clearBuffer(); if(angka_daerah == 1) { pilihdaerahutama(); } else if(angka_daerah == 0) { break; } } while (angka_daerah != 1 && angka_daerah != 0); } void clearBuffer() { int c; while ((c = getchar()) != '\n' && c != EOF); } void daftar_akun_database() { FILE *filePointer; char username[30]; char password[30]; // 直接使用正确的文件名,"a"模式会自动创建不存在的文件 filePointer = fopen("filename.txt", "a"); if (filePointer == NULL) { printf("Gagal membuka file untuk menulis.\n"); return; } printf("Buat username dan password\n"); printf("Username: "); fgets(username, sizeof(username), stdin); // 安全去除换行符:用strcspn找到换行符位置并替换为'\0' username[strcspn(username, "\n")] = '\0'; printf("Password: "); fgets(password, sizeof(password), stdin); password[strcspn(password, "\n")] = '\0'; fprintf(filePointer, "Username: %s\nPassword: %s\n", username, password); printf("Akun berhasil didaftarkan!\n"); fclose(filePointer); // 调用check_database前不需要重新打开文件 check_database(); } void check_database() { FILE *filePointer; char username[50]; char line[50]; char password[50]; bool found = false; int punya_akun; filePointer = fopen("filename.txt", "r"); if (filePointer == NULL) { printf("Gagal membuka file. Silahkan daftar akun terlebih dahulu.\n"); daftar_akun_database(); return; } printf("Masukkan username dan password\n"); printf("Masukkan username: "); fgets(username, sizeof(username), stdin); username[strcspn(username, "\n")] = '\0'; printf("Masukkan password: "); fgets(password, sizeof(password), stdin); password[strcspn(password, "\n")] = '\0'; // 遍历文件查找匹配的账号密码 while (fgets(line, sizeof(line), filePointer) != NULL) { line[strcspn(line, "\n")] = '\0'; if (strstr(line, "Username: ") != NULL && strstr(line, username) != NULL) { // 读取对应的密码行 if (fgets(line, sizeof(line), filePointer) != NULL) { line[strcspn(line, "\n")] = '\0'; if (strstr(line, "Password: ") != NULL && strstr(line, password) != NULL) { printf("Username dan password benar!\n"); found = true; tampilan_awal(); break; } } } } if(!found) { printf("Username atau password tidak dapat ditemukan silahkan coba lagi.\n"); do{ printf("Sudah punya akun? Silahkan ketik 1 untuk daftar dan 2 untuk coba lagi: "); scanf("%i", &punya_akun); clearBuffer(); // 清理scanf后的缓冲区 if(punya_akun == 1) { fclose(filePointer); // 关闭文件再调用其他函数 daftar_akun_database(); return; } else if(punya_akun == 2) { fclose(filePointer); check_database(); return; } else { printf("Input tidak valid!\n"); } } while(true); } fclose(filePointer); } int main() { check_database(); return 0; } // 临时实现未定义的函数,避免编译错误 void pilihdaerahutama() { printf("Anda memilih daerah!\n"); }
问题原因解释
- 缓冲区残留问题:
scanf读取整数时,只会读取数字部分,输入的换行符会留在输入缓冲区。后续fgets会优先读取这个换行符,导致获取的内容为空。解决方法是在scanf之后立即调用clearBuffer清理缓冲区。 - 文件名错误:
fopen的第一个参数是文件名,不是shell命令。使用"touch filename.txt"会尝试打开一个名为该字符串的文件,而非创建filename.txt。fopen的"a"模式会自动创建不存在的文件,无需手动执行touch命令。 clearBuffer调用时机错误:fgets会读取输入直到换行符(并将换行符包含在读取的内容中),此时缓冲区已无多余字符。在fgets之后调用clearBuffer会导致程序等待用户输入更多字符,从而出现多余空行。正确的时机是在scanf之后调用。strtok的安全隐患:如果fgets读取到空字符串(用户直接按回车),strtok会返回NULL,后续使用该字符串会触发未定义行为。改用strcspn可以安全地去除换行符,即使输入为空也不会出错。- 函数返回值错误:
check_database是void类型函数,不能返回整数1,修正为直接返回即可。 - 文件打开检查缺失:
daftar_akun_database中未检查fopen的返回值,若文件打开失败(如权限不足),后续fprintf会导致程序崩溃,新增检查可以避免这种情况。 - 递归调用风险:原代码中多次递归调用
check_database和daftar_akun_database,多次调用后会导致栈溢出。修正后在调用前关闭文件并明确返回,避免递归嵌套过深。
内容的提问来源于stack exchange,提问作者MICHAEL ELBERT JUSTIAN 0000009
相关产品推荐
相关产品推荐

