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
相关产品推荐
相关产品推荐

