malloc后调用memset触发断言失败,添加getchar恢复正常的原因咨询
定义了如下结构体:
struct state { bool isfinal; bool *isfull; char **board; };
其中isfull是一维数组,board是二维数组。编写了如下内存分配函数:
初始版本(运行崩溃)
struct state new_state(struct state state) { int i; struct state new_state = {}; new_state.isfull = (bool *)malloc(BOARD_WIDTH * sizeof(bool)); new_state.board = (char **)malloc(BOARD_HIGHT * sizeof(char *)); for (i = 0; i < BOARD_HIGHT; ++i) new_state.board[i] = (char *)malloc(BOARD_WIDTH * sizeof(char)); if (state.board) { memcpy(new_state.isfull, state.isfull, BOARD_WIDTH * sizeof(bool)); for (i = 0; i < BOARD_HIGHT; ++i) memcpy(new_state.board[i], state.board[i], BOARD_WIDTH * sizeof(char)); } else { memset(new_state.isfull, 0, BOARD_WIDTH * sizeof(bool)); for (i = 0; i < BOARD_HIGHT; ++i) memset(new_state.board[i], 0, BOARD_WIDTH * sizeof(char *)); } return new_state; }
首次调用时传入的state.board为0,执行else分支后触发断言失败,且函数执行到return后程序崩溃。
修复版本1:替换malloc为calloc(问题解决)
struct state new_state(struct state state) { int i; struct state new_state = {}; new_state.isfull = (bool *)calloc(BOARD_WIDTH, sizeof(bool)); new_state.board = (char **)calloc(BOARD_HIGHT, sizeof(char *)); for (i = 0; i < BOARD_HIGHT; ++i) new_state.board[i] = (char *)calloc(BOARD_WIDTH, sizeof(char)); if (state.board) { memcpy(new_state.isfull, state.isfull, BOARD_WIDTH * sizeof(bool)); for (i = 0; i < BOARD_HIGHT; ++i) memcpy(new_state.board[i], state.board[i], BOARD_WIDTH * sizeof(char)); } else { // memset(new_state.isfull, 0, BOARD_WIDTH * sizeof(bool)); // for (i = 0; i < BOARD_HIGHT; ++i) // memset(new_state.board[i], 0, BOARD_WIDTH * sizeof(char *)); } return new_state; }
奇怪现象:添加getchar()延迟后也能正常运行
struct state new_state(struct state state) { int i; struct state new_state = {}; new_state.isfull = (bool *)malloc(BOARD_WIDTH * sizeof(bool)); new_state.board = (char **)malloc(BOARD_HIGHT * sizeof(char *)); for (i = 0; i < BOARD_HIGHT; ++i) new_state.board[i] = (char *)malloc(BOARD_WIDTH * sizeof(char)); if (state.board) { memcpy(new_state.isfull, state.isfull, BOARD_WIDTH * sizeof(bool)); for (i = 0; i < BOARD_HIGHT; ++i) memcpy(new_state.board[i], state.board[i], BOARD_WIDTH * sizeof(char)); } else { getchar(); memset(new_state.isfull, 0, BOARD_WIDTH * sizeof(bool)); for (i = 0; i < BOARD_HIGHT; ++i) memset(new_state.board[i], 0, BOARD_WIDTH * sizeof(char *)); } return new_state; }
已知malloc后调用memset是常规操作,提出两个问题:
- 第一个版本的函数为何无法正常运行?
- 添加getchar()为何能解决问题?
1. 初始版本崩溃的原因
问题出在else分支的memset参数错误:
memset(new_state.board[i], 0, BOARD_WIDTH * sizeof(char *));
new_state.board[i]指向的是BOARD_WIDTH个char的内存块,每个char占1字节,所以memset需要设置的字节数应该是BOARD_WIDTH * sizeof(char),但代码里错误地写成了BOARD_WIDTH * sizeof(char*)。
sizeof(char*)在32位系统是4字节、64位系统是8字节,远大于sizeof(char),这直接导致缓冲区溢出,破坏了堆内存的内部结构。这种错误的后果具有延迟性,可能在函数返回后,后续内存操作触发堆结构校验时才会崩溃。
calloc版本正常的原因是:calloc本身会将分配的内存初始化为0,且你注释掉了错误的memset代码,从根源上避免了溢出问题。
2. 添加getchar()后正常运行的原因
这是未定义行为的典型表现:
缓冲区溢出后,堆内存的损坏位置可能暂时没有被其他关键数据结构占用;添加getchar()后,程序暂停等待输入,期间没有额外的内存操作,堆的损坏没有被触发。或者等待过程中,操作系统的内存调度、堆管理的内部状态发生变化,刚好让损坏区域未影响到后续执行。
这只是巧合,不是真正的修复——内存损坏依然存在,换个运行环境、输入时机,程序大概率还是会崩溃。
内容的提问来源于stack exchange,提问作者amir z

