C语言中3D数组传入函数后,执行fgets时内存地址意外变更
3D数组传参后fgets触发内存地址异常问题
我定义了一个5×5×11的3D char数组,传入insert函数后发现:第一次调用fgets读取用户输入时,数组的内存地址意外发生变化。通过多次printf定位到问题精确发生在fgets(user_input_row, 3, stdin)执行后。按C语言规则,数组传参会退化为指针,其指向的内存地址不应改变,但实际现象与预期不符。
完整测试代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <stddef.h> // for macro NULL int clear_screen() { int pid = fork(); if (pid == 0) { char *newargv[] = { NULL }; char *newenviron[] = { "TERM=screen" }; execve("/usr/bin/clear", newargv, newenviron); } int wait_time = 1; int *ptr_wait_time = &wait_time; wait(ptr_wait_time); return 0; } void init (char char_array[5][5][11]) { printf("char_array in init is at %p which points to %p\n", &char_array, *char_array); printf("char_array[0][0] in init is at %p\n", &char_array[0][0]); printf("char_array[0][0][0] in init is at %p\n", &char_array[0][0][0]); for (int row = 0; row < 5; row++) { for (int col = 0; col < 5; col++) { for (int single_char = 0; single_char < 11; single_char++) { char_array[row][col][single_char] = ' '; } char_array[row][col][10] = '\0'; } } } void display (char char_array[5][5][11]) { printf("\t0\t\t\t\t1\t\t\t\t2\t\t\t\t3\t\t\t\t4\n"); for (int row = 0; row < 5; row++) { printf("%d", row); for (int col = 0; col < 5; col++) { printf("\t%p[%s]", char_array[row][col], char_array[row][col]); } printf("\n"); } } int check_if_int (char *user_maybe_int) { long result; char *endptr; result = strtol(user_maybe_int, &endptr, 10); if (user_maybe_int[0] != '\n' && *endptr == '\n' && result > -1 && result < 5) { return result; } else { return -1; } } char *format_user_input (char *user_input) { int size_of_input; int padding_size; char *formated = malloc(11); size_of_input = strcspn(user_input, "\n") - 1; padding_size = 11 - size_of_input; for (int i = 0; i < size_of_input + 1;i ++) { formated[i] = user_input[i]; } for (int i = size_of_input + 1; i < 11;i ++) { formated[i] = ' '; } formated[10] = '\0'; return formated; } int insert (char char_array[5][5][11]) { printf("char_array in insert is at %p which points to %p\n", &char_array, *char_array); printf("char_array[0][0] in insert is at %p and points to %p\n", &char_array[0][0], char_array[0][0]); printf("char_array[0][0][0] in insert is at %p\n", &char_array[0][0][0]); char user_input_row[2]; char user_input_col[2]; printf("did char_array[0][0] changed1? %p points to %p (the answer is : not yet)\n", &char_array[0][0], char_array[0][0]); int row; printf("did char_array[0][0] changed2? %p points to %p (the answer is : not yet)\n", &char_array[0][0], char_array[0][0]); int col; printf("did char_array[0][0] changed3? %p points to %p (the answer is : not yet)\n", &char_array[0][0], char_array[0][0]); char user_input[11] = ""; printf("did char_array[0][0] changed4? %p points to %p (the answer is : not yet)\n", &char_array[0][0], char_array[0][0]); printf("insert in row : "); printf("did char_array[0][0] changed5? %p points to %p (the answer is : not yet)\n", &char_array[0][0], char_array[0][0]); fgets(user_input_row, 3, stdin); printf("did char_array[0][0] changed6? %p points to %p (the answer is : yes, most of the time)\n", &char_array[0][0], char_array[0][0]); row = check_if_int(user_input_row); if (row == -1) { return 1; } printf("insert in column : "); fgets(user_input_col, 3, stdin); col = check_if_int(user_input_col); if (col == -1) { return 1; } printf("your input : "); fgets(user_input, 12, stdin); char *user_input_formated = format_user_input(user_input); //actual insert printf("User try to insert at %p via char_array[%d][%d]\n", &char_array[row][col], row, col); strncpy(char_array[row][col], user_input_formated, 11); free(user_input_formated); return 0; } void start (char char_array[5][5][11]) { //clear_screen(); int insert_res; // this function display a grid (memory address + content) display(char_array); insert_res = insert(char_array); if (insert_res != 1) { start(char_array); } else { printf("something went wrong on insert!"); exit(1); } } int main () { // 3d array // we define 5 x 5 array of arrays of chars (10 chars + null terminator) // for a visual reprensentation, 2d arrays give columns, 3d arrays add rows char char_array[5][5][11]; // this function fills each array of chars with "0000000000\0" init(char_array); // start the display/insert recursion start(char_array); }
运行输出
char_array in insert is at 0x7ffc4e60cd70 which points to 0x7ffc4e60cdb0 char_array[0][0] in insert is at 0x7ffc4e60cdb0 and points to 0x7ffc4e60cdb0 char_array[0][0][0] in insert is at 0x7ffc4e60cdb0 did char_array[0][0] changed1? 0x7ffc4e60cdb0 points to 0x7ffc4e60cdb0 (the answer is : not yet) did char_array[0][0] changed2? 0x7ffc4e60cdb0 points to 0x7ffc4e60cdb0 (the answer is : not yet) did char_array[0][0] changed3? 0x7ffc4e60cdb0 points to 0x7ffc4e60cdb0 (the answer is : not yet) did char_array[0][0] changed4? 0x7ffc4e60cdb0 points to 0x7ffc4e60cdb0 (the answer is : not yet) insert in row : did char_array[0][0] changed5? 0x7ffc4e60cdb0 points to 0x7ffc4e60cdb0 (the answer is : not yet) 0 did char_array[0][0] changed6? 0x7ffc4e60cd00 points to 0x7ffc4e60cd00 (the answer is : yes, most of the time) did char_array[0][0] changed7? 0x7ffc4e60cd00 points to 0x7ffc4e60cd00 (the answer is : yes, most of the time) insert in column : did char_array[0][0] changed8? 0x7ffc4e60cd00 points to 0x7ffc4e60cd00 (the answer is : yes, most of the time) 0 did char_array[0][0] changed9? 0x7ffc4e60cd00 points to 0x7ffc4e60cd00 (the answer is : yes, most of the time) did char_array[0][0] changed10? 0x7ffc4e60cd00 points to 0x7ffc4e60cd00 (the answer is : yes, most of the time) your input : a did char_array[0][0] changed11? 0x7ffc4e60cd00 points to 0x7ffc4e60cd00 (the answer is : yes, most of the time) User try to insert at 0x7ffc4e60cd00 via char_array[0][0]
问题根源与修复方案
问题本质:栈溢出导致指针被篡改
insert函数中定义的user_input_row[2]数组长度为2,但调用fgets(user_input_row, 3, stdin)时要求最多读取3个字符(包括换行符和终止符'\0')。当用户输入类似0\n时,fgets会写入'0'、'\n'、'\0'三个字符,超出数组的2字节容量,触发栈缓冲区溢出。
由于char_array作为函数参数退化为指针(类型为char (*)[5][11]),存储在栈上且位置与user_input_row相邻,溢出的字符会直接覆盖char_array指针的值,导致后续打印的数组地址异常变化。
修复步骤
- 调整局部数组大小:将
user_input_row和user_input_col的长度改为3,确保能容纳输入的字符、换行符和终止符:char user_input_row[3]; char user_input_col[3]; - 规范fgets调用:使用
sizeof获取数组实际长度,避免硬编码长度导致的越界:fgets(user_input_row, sizeof(user_input_row), stdin); fgets(user_input_col, sizeof(user_input_col), stdin);
修改后,栈缓冲区不再溢出,char_array指针的值不会被篡改,数组的内存地址也会保持稳定。
内容的提问来源于stack exchange,提问作者fun_times
相关产品推荐
相关产品推荐

