为何我的C语言密码生成器代码出现内存损坏问题?
我用C语言开发了一款可自定义长度和字符集的密码生成器,测试发现执行passgen 10时能正常生成包含全字符的10位密码,但执行passgen 10 include-numbers时输出乱码(如9l724è470`),猜测是rand函数越界访问内存导致内存损坏,但找不到具体原因。相关代码如下:
#include "str2int.h" #include <stdio.h> #include <stdlib.h> #include <time.h> #define CAPITALS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" #define SMALL "abcdefghijklmonpqrstuvwxyz" #define ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmonpqrstuvwxyz" #define NUMBERS "1234567890" #define SPECIALS "!#$@&" #define ALLCHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmonpqrstuvwxyz1234567890!#$@&" int passlength, errcode; char* characters; int numinclude = 0, specialinclude = 0, alphainclude = 0, capitalinclude = 0, smallinclude = 0; int main(int argc, char* argv[]) { srand(time(NULL)); if (argc > 1) { passlength = str2int(argv[1]); errcode = errorCheck(); if (errcode != 0) { printf("passgen %s\n", argv[1]); printf(" ^ Here\n"); printf("Invalid input for integer"); return 0; } } else { printf("Usage: passgen <passlength> [add-all] [include-numbers] [include-specials] [include-alphabet] [include-capitals] [include-small]"); printf("\n[] = Optional, <> = Required\n"); printf("Options:\n"); printf(" add-all: Includes all characters. Default option if no flags are provided.\n"); printf(" include-numbers: Adds numbers to the list of usable characters.\n"); printf(" include-specials: Adds special characters (!#$@&) to the list of usable characters.\n"); printf(" include-alphabet: Adds the entire alphabet (including capital and small letters) to the list of usable characters.\n"); printf(" include-capitals: Adds the capital alphabet to the list of usable characters.\n"); printf(" include-small: Adds the small alphabet to the list of usable characters.\n"); return 0; } if (argc > 2) { for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "add-all") == 0) { characters = (char*) realloc(characters, 68); strcat(characters, ALLCHARS); break; } if (strcmp(argv[i], "include-numbers") == 0) { if (numinclude == 0) { characters = (char*) realloc(characters, 11); strcat(characters, NUMBERS); numinclude = 1; } } if (strcmp(argv[i], "include-specials") == 0) { if (specialinclude == 0) { characters = (char*) realloc(characters, 6); strcat(characters, SPECIALS); specialinclude = 1; } } if (strcmp(argv[i], "include-alphabet") == 0) { if (alphainclude == 0) { characters = (char*) realloc(characters, 53); strcat(characters, ALPHABET); alphainclude = 1; capitalinclude = 1; smallinclude = 1; } } if (strcmp(argv[i], "include-capitals") == 0) { if (capitalinclude == 0) { characters = (char*) realloc(characters, 27); strcat(characters, CAPITALS); capitalinclude = 1; if (capitalinclude && smallinclude) { alphainclude = 1; } } } if (strcmp(argv[i], "include-small") == 0) { if (smallinclude == 0) { characters = (char*) realloc(characters, 27); strcat(characters, SMALL); smallinclude = 1; if (smallinclude && capitalinclude) { alphainclude = 1; } } } } goto generate; } characters = (char*) realloc(characters, 68); characters = ALLCHARS; generate: printf("Generating password with length %d\n", passlength); for (int x = 0; x < passlength; x++){ printf("%c", characters[rand() % ((int) strlen(characters))]); } }
问题根源分析
未初始化指针引发的strcat错误
characters是全局指针,默认初始化为NULL。第一次调用realloc(NULL, 11)会分配11字节内存,但这块内存是未初始化的垃圾数据,没有字符串终止符\0。strcat需要从目标字符串的\0位置开始追加内容,此时它会越过分配的内存去寻找终止符,直接导致越界访问,破坏内存结构,最终输出乱码。realloc大小计算错误
每次添加字符集时,只按单个字符集的大小分配内存,没有考虑已经存在的字符长度。比如如果同时添加数字和特殊字符,第二次realloc的大小只给了6字节,根本装不下之前的数字加新的特殊字符,必然导致内存溢出。默认分支的指针赋值错误
characters = ALLCHARS;直接把字符串常量的地址赋值给指针,之前realloc分配的内存被泄漏。虽然这个分支暂时能正常运行,但如果后续尝试修改characters,会触发只读内存错误。遗漏必要头文件
原代码没有包含<string.h>,strcmp、strcat、strlen这些函数的声明缺失,会导致编译器隐式声明,可能引发未定义行为。
修复方案
初始化字符集指针
把characters初始化为空字符串,确保有\0终止符:char* characters = calloc(1, 1); // 分配1字节并初始化为0,即\0正确计算内存分配大小
每次添加新字符集时,计算当前字符集长度 + 新字符集长度 + 1(终止符),再重新分配内存:// 以添加数字为例 if (strcmp(argv[i], "include-numbers") == 0) { if (numinclude == 0) { size_t current_len = strlen(characters); size_t num_len = strlen(NUMBERS); characters = realloc(characters, current_len + num_len + 1); if (!characters) { // 检查内存分配是否成功 perror("realloc failed"); return 1; } strcat(characters, NUMBERS); numinclude = 1; } }修正默认分支的赋值逻辑
不要直接赋值字符串常量,改用strcpy把内容复制到分配好的内存中:size_t all_len = strlen(ALLCHARS); characters = realloc(characters, all_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcpy(characters, ALLCHARS);添加内存分配检查
每次realloc或calloc后都要检查返回值是否为NULL,避免空指针操作导致程序崩溃。释放内存避免泄漏
在程序结束前调用free(characters),释放分配的内存。
修复后的完整代码
#include "str2int.h" #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #define CAPITALS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" #define SMALL "abcdefghijklmnopqrstuvwxyz" // 修正原宏拼写错误(mon→mn) #define ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" #define NUMBERS "1234567890" #define SPECIALS "!#$@&" #define ALLCHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!#$@&" // 示例str2int和errorCheck实现,可根据实际情况调整 int str2int(const char* s) { return atoi(s); } int errorCheck() { // 可添加实际错误检查逻辑,比如判断passlength是否为正整数 return 0; } int main(int argc, char* argv[]) { srand(time(NULL)); int passlength = 0, errcode = 0; char* characters = calloc(1, 1); // 初始化为空字符串 int numinclude = 0, specialinclude = 0, alphainclude = 0, capitalinclude = 0, smallinclude = 0; if (!characters) { perror("calloc failed"); return 1; } if (argc > 1) { passlength = str2int(argv[1]); errcode = errorCheck(); if (errcode != 0 || passlength <= 0) { printf("passgen %s\n", argv[1]); printf(" ^ Here\n"); printf("Invalid input: password length must be a positive integer\n"); free(characters); return 0; } } else { printf("Usage: passgen <passlength> [add-all] [include-numbers] [include-specials] [include-alphabet] [include-capitals] [include-small]\n"); printf("[] = Optional, <> = Required\n"); printf("Options:\n"); printf(" add-all: Includes all characters. Default option if no flags are provided.\n"); printf(" include-numbers: Adds numbers to the list of usable characters.\n"); printf(" include-specials: Adds special characters (!#$@&) to the list of usable characters.\n"); printf(" include-alphabet: Adds the entire alphabet (including capital and small letters) to the list of usable characters.\n"); printf(" include-capitals: Adds the capital alphabet to the list of usable characters.\n"); printf(" include-small: Adds the small alphabet to the list of usable characters.\n"); free(characters); return 0; } if (argc > 2) { for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "add-all") == 0) { size_t all_len = strlen(ALLCHARS); characters = realloc(characters, all_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcpy(characters, ALLCHARS); break; } if (strcmp(argv[i], "include-numbers") == 0) { if (numinclude == 0) { size_t current_len = strlen(characters); size_t num_len = strlen(NUMBERS); characters = realloc(characters, current_len + num_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcat(characters, NUMBERS); numinclude = 1; } } if (strcmp(argv[i], "include-specials") == 0) { if (specialinclude == 0) { size_t current_len = strlen(characters); size_t spec_len = strlen(SPECIALS); characters = realloc(characters, current_len + spec_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcat(characters, SPECIALS); specialinclude = 1; } } if (strcmp(argv[i], "include-alphabet") == 0) { if (alphainclude == 0) { size_t current_len = strlen(characters); size_t alpha_len = strlen(ALPHABET); characters = realloc(characters, current_len + alpha_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcat(characters, ALPHABET); alphainclude = 1; capitalinclude = 1; smallinclude = 1; } } if (strcmp(argv[i], "include-capitals") == 0) { if (capitalinclude == 0) { size_t current_len = strlen(characters); size_t cap_len = strlen(CAPITALS); characters = realloc(characters, current_len + cap_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcat(characters, CAPITALS); capitalinclude = 1; if (capitalinclude && smallinclude) { alphainclude = 1; } } } if (strcmp(argv[i], "include-small") == 0) { if (smallinclude == 0) { size_t current_len = strlen(characters); size_t small_len = strlen(SMALL); characters = realloc(characters, current_len + small_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcat(characters, SMALL); smallinclude = 1; if (smallinclude && capitalinclude) { alphainclude = 1; } } } } } else { // 默认添加所有字符 size_t all_len = strlen(ALLCHARS); characters = realloc(characters, all_len + 1); if (!characters) { perror("realloc failed"); return 1; } strcpy(characters, ALLCHARS); } printf("Generating password with length %d\n", passlength); for (int x = 0; x < passlength; x++){ int char_count = strlen(characters); printf("%c", characters[rand() % char_count]); } printf("\n"); // 添加换行符,输出更规范 free(characters); return 0; }
内容的提问来源于stack exchange,提问作者TimeSquared

