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

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指针的值,导致后续打印的数组地址异常变化。

修复步骤

  1. 调整局部数组大小:将user_input_row和user_input_col的长度改为3,确保能容纳输入的字符、换行符和终止符:
    char user_input_row[3];
    char user_input_col[3];
    
  2. 规范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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 01:14:59