You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言fgets存储用户名失败问题排查及修正请求

问题描述

使用C语言编写的代码在通过fgets获取用户名并存储到文件时出现以下异常:

  • 文件中仅显示空的Username行和Password行
  • 输入操作时会出现多余空行
    怀疑问题与fgets函数、clearBuffer函数有关,临近提交截止日期,需要指出代码错误、提供修正后的代码并解释问题原因。
错误分析
  • scanf读取输入后未清理缓冲区,导致后续fgets直接读取残留的换行符,获取空内容
  • fopen的文件名错误使用了shell命令格式"touch filename.txt",无法正确打开/创建目标文件filename.txt
  • clearBuffer函数调用时机错误:在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");
}
问题原因解释
  1. 缓冲区残留问题:scanf读取整数时,只会读取数字部分,输入的换行符会留在输入缓冲区。后续fgets会优先读取这个换行符,导致获取的内容为空。解决方法是在scanf之后立即调用clearBuffer清理缓冲区。
  2. 文件名错误:fopen的第一个参数是文件名,不是shell命令。使用"touch filename.txt"会尝试打开一个名为该字符串的文件,而非创建filename.txt。fopen的"a"模式会自动创建不存在的文件,无需手动执行touch命令。
  3. clearBuffer调用时机错误:fgets会读取输入直到换行符(并将换行符包含在读取的内容中),此时缓冲区已无多余字符。在fgets之后调用clearBuffer会导致程序等待用户输入更多字符,从而出现多余空行。正确的时机是在scanf之后调用。
  4. strtok的安全隐患:如果fgets读取到空字符串(用户直接按回车),strtok会返回NULL,后续使用该字符串会触发未定义行为。改用strcspn可以安全地去除换行符,即使输入为空也不会出错。
  5. 函数返回值错误:check_database是void类型函数,不能返回整数1,修正为直接返回即可。
  6. 文件打开检查缺失:daftar_akun_database中未检查fopen的返回值,若文件打开失败(如权限不足),后续fprintf会导致程序崩溃,新增检查可以避免这种情况。
  7. 递归调用风险:原代码中多次递归调用check_database和daftar_akun_database,多次调用后会导致栈溢出。修正后在调用前关闭文件并明确返回,避免递归嵌套过深。

内容的提问来源于stack exchange,提问作者MICHAEL ELBERT JUSTIAN 0000009

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 08:05:54