Search code examples
cfgets

when passing a 3d array to a function, the memory address of the array suddenly change after performing an fget


as the title mention it : when passing a 3d array to a function, the memory address of the array suddenly change after performing an fget

the issue occurs in the insert function right after the fgets

#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);
}

as you can see from the multiples printf, I tried to pinpoint the exact moment where the memory address changed, from my understanding, char_array decays to a pointer when passed to this function and the memory address should not change (obviously, i'm missing something because it does change after performing the 1st fgets)

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]


Solution

  • #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main () {
      char too_small_buffer[2];
      char ok_buffer[3];
      printf("enter one char in too small buffer : ");
      // if you enter "a" and press enter :
      // undefined behavior starts here since fgets 
      // does not have enough room to store 'a' '\n' '\0'
      fgets(too_small_buffer, 3, stdin);
      printf("decimal stored in too_small_buffer[0] : %d\n", too_small_buffer[0]);
      printf("decimal stored in too_small_buffer[1] : %d\n", too_small_buffer[1]);
      printf("enter one char in ok buffer : ");
      // this would work fine if there was no buffer overflow above
      fgets(ok_buffer, 3, stdin);
      printf("decimal stored in ok_buffer[0] : %d\n", ok_buffer[0]);
      printf("decimal stored in ok_buffer[1] : %d\n", ok_buffer[1]);
      printf("decimal stored in ok_buffer[2] : %d\n", ok_buffer[2]);
    }
    

    so fgets reads n-1 ("a\n") and try to add '\0' but does not have enough space.

    credit to @weather-vane in the comments above who have spotted the issue immediately.

    It seems to work fine for this program (the sample program in this answer), but it caused an issue in the initial program.

    enter one char in too small buffer : a
    decimal stored in too_small_buffer[0] : 97
    decimal stored in too_small_buffer[1] : 10
    enter one char in ok buffer : a
    decimal stored in ok_buffer[0] : 97
    decimal stored in ok_buffer[1] : 10
    decimal stored in ok_buffer[2] : 0