-1

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]

fun_times
  • 134
  • 6
  • 2
    You have `fgets(user_input_col, 3, stdin);` but the buffer `char user_input_col[2];` is too small. Anyway, `char user_input_col[2];` can only store one a one-character string, and as `fgets()` wants to retain the newline (if there is room) then this buffer isn't very useful. Ditto for `user_input_row`. – Weather Vane May 13 '23 at 10:35
  • 1
    ... so you are likely to be getting buffer overflow, and all bets are off. You've done a similar thing with `char user_input[11] = "";` and `fgets(user_input, 12, stdin);`. A better way would be `fgets(user_input, sizeof user_input, stdin);` but you need bigger buffers, and to be aware of (and [remove](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input/28462221#28462221)) the newline in the input strings. If you want to input strings of length 11 the buffer must be at least 13 bytes, and preferably *much* larger, to allow for mistyping. – Weather Vane May 13 '23 at 10:48
  • this part of the code works fine, and there is an extra check on user input, for both row, column and input. The C library function char *fgets(char *str, int n, FILE *stream) reads a line from the specified stream and stores it into the string pointed to by str. It stops when either (n-1) characters are read, the newline character is read, or the end-of-file is reached, whichever comes first. so fgets store "1\n" and extra checks are made to convert to an int. are you sure this is my problem? – fun_times May 13 '23 at 10:50
  • Sorry, it might *appear* to work fine, but the buffer overflow might be causing the strange side-effects you are asking about. Fix the buffer overflow, and use bigger buffers. You are allowing `fgets` to use buffers 1 byte larger than they actually are. – Weather Vane May 13 '23 at 10:51
  • fgets stop reading at n-1, how can this cause a buffer overflow? – fun_times May 13 '23 at 10:52
  • 3
    The `size` you give to `fgets()` is the size of the buffer it can use, end of story. You allocated a buffer size 2 (which isn't even large enough to enter anything useful, and told `fgets` its size is 3. That is simply wrong. – Weather Vane May 13 '23 at 10:53
  • thank you, I misunderstood the "It stops when either (n-1) characters are read" from the fgets manual – fun_times May 13 '23 at 10:59
  • Good, now use a bigger buffer: the two bytes can only hold an "empty string", i.e. a `'\n'` newline and a `'\0'` string terminator. – Weather Vane May 13 '23 at 11:02
  • it can also hold "1\n" and trash the \0 no? let's say if you want to null terninate your string later – fun_times May 13 '23 at 11:04
  • 1
    No, it always hold a string terminator. Suppose user enters `"1\n"` then `fgets` will store `"1\0"` and the newline will stay in the buffer, to be read as an empty line for the next input. Use bigger buffers, you do not have to pay for them. Use *much* bigger buffers. – Weather Vane May 13 '23 at 11:06
  • first, thanks a lot for helping me, it did solve my issue indeed. but regarding your last statement, I am unsure, a fgets with size 3 on a buffer of size 2 will actually capture the newline ``` #include #include #include int main () { char small_buffer[2]; printf("enter something : "); fgets(small_buffer, 3, stdin); printf("small buffer contains : %d\n", small_buffer[0]); printf("small buffer contains : %d\n", small_buffer[1]); } ``` – fun_times May 13 '23 at 11:40
  • 2
    "fgets with size 3 on a buffer of size 2" causes undefined behaviour. Just use the right buffer size, of a large enough buffer. – Weather Vane May 13 '23 at 11:50
  • got it, I just wanted to test, because I was pretty sure the newline was captured. – fun_times May 13 '23 at 11:52
  • 1
    It might be. Please see [Undefined, unspecified and implementation-defined behavior](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior). The result might be what you wanted, a system crash, peculaiar behaviour somewhere else... anything, because what happens is not defined. What you must not do, is to use "oh it worked anyway" as an excuse to break the rules, because on another machine, at the exam or presentation, it won't work. – Weather Vane May 13 '23 at 11:55
  • you are right it is causing a buffer overflow (you already know this) but in your explanation, you say fgets will store `"1\0"`, and the newline will stay in the buffer. I believe this is incorrect. Based on my test, the newline will get captured in small_buffer[1]. I think it is fgets trying to append `"\0"` in position small_buffer[2] that will cause the buffer overflow – fun_times May 15 '23 at 07:01
  • so my error was not misreading the n - 1 part, it was ignoring the fact that fgets would try to append `'\0'` – fun_times May 15 '23 at 07:08
  • `fgets` does not append anything. It stores the incoming characters in the given array until a newline was received, or there are `n-1` characters stored in the array. It then *always* writes a `'\0'`. Any characters that are still in the input buffer remain there, until the next input. If you have overflowed the array (which you have) then you can't make any valid observations about whether or not you saw a newline. – Weather Vane May 15 '23 at 16:31
  • when I said "append" it had to me the same meaning as when you say "write" but I get your point now. even if I "see" a newline, my observations are invalid because it is UB/Implementation specific. I retested without overflowing the array (with a correct buffer of 3 chars) but my point is that the bytes are appended in the following order '1' '\n' '\0' in your previous comment, you said that it would store `"1\0"` and leave the new line in the buffer. I think (based on the test with the correct buffer size) that it stores `"1\n"` and then overflow the array trying to write `'\0'` – fun_times May 16 '23 at 11:57
  • added the fgets tag to this question to help discoverability by others since my issue had nothing to do with 3d arrays and memory addresses and everything to do with fgets – fun_times May 16 '23 at 12:01
  • Since your question is still open, and you have zeroed your focus on `fgets` it will be good for you to get ahead of the conversation. Become an expert on what the `man` pages (or other documentation) say about it. Once you truly know how _it_ works, and make corresponding adjustments to your code, the core issues discussed here will have likely been resolved. – ryyker May 16 '23 at 12:15
  • I read the manual, understand it and my issue is indeed resolved since the beginning of this conversation will look into closing it. – fun_times May 16 '23 at 12:25
  • I cannot close it since there is only comments and no answers – fun_times May 16 '23 at 12:33

1 Answers1

0
#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
fun_times
  • 134
  • 6