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

C语言密码管理器调用gets()函数时崩溃问题排查

C语言密码管理器程序崩溃原因分析

问题描述

正在为学校作业编写一个C语言密码管理器程序,但执行Inputdata()函数时程序崩溃。该函数在case 1分支中被调用,用于录入密码记录信息,包含gets()读取字符串、scanf()读取ID的逻辑。相关代码及完整程序如下:

调用分支代码

case 1:
        // new record adding
        s = Inputdata();
        fwrite(&s, sizeof(password), 1, f);
        sA[d] = s;
        d++;
        break;

Inputdata()函数代码

password Inputdata()
{
    password s;
    fflush(stdin); // clearing memory buffer
    printf("\nEnter name of the site for the password (Ex. Gmail password)\t:\t");
    gets(s.name);
    printf("\nEnter unique password identifier.   \t:\t");
    scanf("%d", &s.ID);
    printf("\nEnter password to create\t:\t");
    gets(s.pw);
    return s;
}

password结构体定义

struct password
{
    // Data Members
    char name[30];
    int ID;     // password ID
    char pw[30]; //storage of password
};
typedef struct password password;

完整程序代码

// password record Management using file /array/function
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
// structure of password details  composite structure by using mark structure
struct password
{
    // Data Members
    char name[30];
    int ID;     // password ID
    char pw[30]; //storage of password
};
typedef struct password password;
// input block
password Inputdata()
{
    password s;
    fflush(stdin); // clearing memory buffer
    printf("\nEnter name of the site for the password (Ex. Gmail password)\t:\t");
    gets(s.name);
    printf("\nEnter unique password identifier.   \t:\t");
    scanf("%d", &s.ID);
    printf("\nEnter password to create\t:\t");
    gets(s.pw);
    return s;
}
// output block
void showdata(password s)
{
    printf("\n%s\t\t%d\t%s", s.name, s.ID, s.pw);
}
// find a password according to IDs
int findpass(password s, int i)
{
    if (s.ID == i)
    {
        showdata(s);
        return 1;
    }
    else
    {
        return 0;
    }
}
// menu displaying
int menu()
{
    int choice;
    system("cls"); // for clearing screen
    printf("\n|Password Manager - C2000 v1.0|\n");
    printf("1. Add new password \n");
    printf("2. Delete password \n");
    printf("3. Find password\n");
    printf("4. Show all the password \n");
    printf("7. Showing Sorted IDs\n");
    printf("0. Quit\n");
    printf("Pick one : ");
    scanf("%d", &choice);
    return choice;
}
// deleting a record from array
void delData(password *s, int cur, int di)
{
    int found = 0, i, j;
    for (i = 0; i < cur; i++)
    {
        found = findpass(s[i], di);
        if (found == 1)
            break; // break statement outside the switch and used in loop
    }
    if (found == 0)
        printf("No Record Found");
    else
        for (j = i; j < cur - 1; j++)
        {
            s[j] = s[j + 1];
        }
}
password s; // global variable
// main driver program
int main()
{
    // first of all making binary file
    FILE *f = fopen("password1.dat", "wb+"); // file oprn
    int id, pw, i = 0, j;
    int d;
    if (!f)
    {
        printf("Cannot open file!\n");
        return 1;
    }
    printf("|Password Manager - C2000 v1.0|\n ");
    printf("\nPress any key to start...\n");
    // loading array from binary file
    password *sA = (password *)malloc(d * sizeof(password)); // dynamic memory initialization of array
    fseek(f, 0, SEEK_SET);
    password k, temp;
    for (i = 0; i < d; i++)
    {
        fread((char *)&sA[i], sizeof(s), 1, f); // loading array from file
        showdata(sA[i]);                        // showing array
    }
    getch();
    // loop will execute until not exit
    while (1)
    {
        int ch = menu();
        switch (ch)
        {
        case 1:
            // new record adding
            s = Inputdata();
            fwrite(&s, sizeof(password), 1, f);
            sA[d] = s;
            d++;
            break;
        case 2: // delete from array
        {
            int di, found = 0;
            printf("\n Enter ID to Delete\t:\t");
            scanf("%d", &di);
            delData(sA, d, di);
            d--;
            break;
        }
        case 3: // find password
        {
            int di, found = 0;
            printf("\n Enter ID to Delete\t:\t");
            scanf("%d", &di);
            for (i = 0; i < d; i++)
            {
                found = findpass(sA[i], di);
            }
            if (found == 0)
                printf("No Record Found");
            break;
        }
        case 4: // print all
            for (i = 0; i < d; i++)
            {
                fread((char *)&sA[i], sizeof(s), 1, f); // loading array from file
                showdata(sA[i]);
            }
            getch();
            break;
        case 5:
            // sorting of array and displaying
            for (i = 0; i < d - 1; i++)
            {
                for (j = i + 1; j < d; j++)
                {
                    if (sA[i].ID > sA[j].ID) // checking and swapping
                    {
                        temp = sA[i];
                        sA[i] = sA[j];
                        sA[j] = temp;
                    }
                }
            }
            // showing sorted data on screen
            for (i = 0; i < d; i++)
            {
                showdata(sA[i]);
            }
            break;
        default:
            exit(0);
        }
        getch();
    }
    free(sA);
}

崩溃原因分析

1. 未初始化变量d引发致命内存越界

main()函数中,变量d仅声明未初始化,其值为随机垃圾值:

int d;
password *sA = (password *)malloc(d * sizeof(password));

这会导致:

  • malloc分配的内存大小完全不可控,可能远小于实际需要,或分配过大内存浪费资源
  • 后续执行sA[d] = s时,直接访问随机内存地址,触发段错误(内存访问违规),这是程序崩溃的核心原因。

2. scanf()与gets()的输入缓冲区冲突

在Inputdata()函数中:

  • scanf("%d", &s.ID)读取整数后,输入缓冲区会残留用户按下的换行符
  • fflush(stdin)是未定义行为(C标准仅允许fflush操作输出流),无法清空输入缓冲区
  • 后续调用gets(s.pw)时,会直接读取残留的换行符作为输入,导致s.pw被设为空字符串,甚至引发缓冲区溢出。

3. gets()函数的安全性缺陷

gets()函数不检查输入长度,若用户输入的字符串超过name或pw数组的30字节长度,会直接溢出缓冲区,破坏栈内存结构,引发程序崩溃或内存损坏。gets()已被C11标准废弃,属于高危函数。

4. 文件初始化读取逻辑错误

初始化阶段读取文件内容的循环:

for (i = 0; i < d; i++)
{
    fread((char *)&sA[i], sizeof(s), 1, f);
    showdata(sA[i]);
}

此时d是未初始化的垃圾值,循环次数随机,会导致读取超出文件范围的内容,或越界写入sA数组,进一步破坏内存。

修复方案

1. 初始化变量并动态扩容数组

将d初始化为0,添加新记录时用realloc动态扩容数组:

int d = 0; // 初始化记录数量为0
password *sA = NULL; // 初始化为空指针

// 在case 1分支中修改:
case 1:
    s = Inputdata();
    fwrite(&s, sizeof(password), 1, f);
    // 扩容数组,检查realloc是否成功
    password *temp = (password *)realloc(sA, (d+1)*sizeof(password));
    if (temp != NULL) {
        sA = temp;
        sA[d] = s;
        d++;
    } else {
        printf("内存分配失败!");
    }
    break;

2. 替换gets()为安全的fgets(),正确处理输入缓冲区

用fgets()替代gets(),并手动清空scanf()残留的换行符:

password Inputdata()
{
    password s;
    int c;
    // 清空输入缓冲区残留的字符
    while ((c = getchar()) != '\n' && c != EOF);

    printf("\nEnter name of the site for the password (Ex. Gmail password)\t:\t");
    fgets(s.name, sizeof(s.name), stdin);
    // 去掉fgets读取的换行符
    s.name[strcspn(s.name, "\n")] = '\0';

    printf("\nEnter unique password identifier.   \t:\t");
    scanf("%d", &s.ID);

    // 再次清空scanf残留的换行符
    while ((c = getchar()) != '\n' && c != EOF);

    printf("\nEnter password to create\t:\t");
    fgets(s.pw, sizeof(s.pw), stdin);
    s.pw[strcspn(s.pw, "\n")] = '\0';

    return s;
}

3. 修复文件初始化读取逻辑

先统计文件中的记录数,再分配内存读取:

// 统计文件中的记录数
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
d = file_size / sizeof(password);
fseek(f, 0, SEEK_SET);

// 分配内存(需检查malloc是否成功)
sA = (password *)malloc(d * sizeof(password));
if (sA == NULL) {
    printf("内存分配失败!");
    fclose(f);
    return 1;
}

// 读取文件内容
for (i = 0; i < d; i++) {
    fread(&sA[i], sizeof(password), 1, f);
    showdata(sA[i]);
}

4. 移除fflush(stdin)

用while ((c = getchar()) != '\n' && c != EOF);替代fflush(stdin),这是清空输入缓冲区的标准做法。


内容的提问来源于stack exchange,提问作者Christian D'Albano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 14:10:39