0

I'm trying to create a pointer array that holds strings of various sizes. I want to prompt the user for input, store the values in the array, and then print them using the following code:

#include <stdio.h>

int main() {

    char *string_array[10];

    for (int x = 0; x < 2; x++)
    {
        char string[50];
        printf("Input Name of Fruit number %d\n", x);
        fgets(string, 50, stdin);
        string_array[x] = string;
    }

    for (int x = 0; x < 2; x++)
    {
        printf("%s\n", string_array[x]);
    }
    return 0;
}

I expected this code to take in two user prompts and store the address of each string. However, when I run the code, the final user prompt represents the value stored at each address in the pointer array. On inspection, I found that every address is the same.

I presume this is because the string variable is instantiated to the same address every time, or is never changed despite being defined in each for loop. What's happening here with the addresses to prevent this code from working?

Connor
  • 867
  • 7
  • 18
  • 2
    `char string[50];` defines a *local* variable inside the loop itself. Each iteration of the loop, and when the loop ends, the life-time of this variable ends, leaving you with invalid pointers. – Some programmer dude Feb 19 '23 at 11:29
  • 2
    It's actually *undefined behaviour*. You are storing the same address of the same buffer, but when you report the strings, that buffer has gone out of scope. What you can do is to make a copy: `string_array[x] = strdup(string);` – Weather Vane Feb 19 '23 at 11:29
  • 1
    Aside: when you have that working, you'll notice that a blank line separates each string you print, because `fgets()` retains the newline (if there is room). Please see [Removing trailing newline character from fgets() input](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input/28462221#28462221) – Weather Vane Feb 19 '23 at 11:34
  • @WeatherVane Okay, so `strdup` duplicates the string, puts it in a new memory address and assigns that memory address to the pointer array. If the `strdup` call is within the for loop too, why doesn't that new memory address go out of scope? – Connor Feb 19 '23 at 13:50
  • @WeatherVane What does reporting a string mean? – Connor Feb 19 '23 at 13:51
  • 2
    Because behind the `strdup` allocates memory dynamically, which does not go out of scope. Local stack variables go out of scope. I meant the last loop of the program is reporting what it did. – Weather Vane Feb 19 '23 at 14:59
  • 1
    `strdup()` uses `malloc()` to allocate memory dynamically on the heap. The allocated memory does not go out of scope without a deliberate call to `free()`, or until the program exits. – Harith Feb 19 '23 at 16:58

3 Answers3

2
for (int x = 0; x < 2; x++)
{
        char string[50];
        printf("Input Name of Fruit number %d\n", x);
        fgets(string, 50, stdin);
        string_array[x] = string;
}

The array string in this loop has block scope. It ceases to exist when the block ends. So string_array[0] and string_array[1] will be pointing to memory that no longer exists, and accessing that memory would invoke undefined behaviour.

  1. Objects have a lifetime outside of which they can't be accessed.

  2. Referring to an object outside of its lifetime has undefined behaviour.

  3. Automatic variables have a lifetime corresponding to the execution of the their block of definition.


Fix:

As commented by @Weather Vane:

You are storing the same address of the same buffer, but when you report the strings, that buffer has gone out of scope. What you can do is to make a copy:

string_array[x] = strdup(string);

Another solution would be to allocate memory with malloc() and use standard strcpy(). The memory can later be resized with realloc() if the input exceeds the original length of the block.

Harith
  • 4,663
  • 1
  • 5
  • 20
  • What are "automatic variables"? Why does `strdup` work within the loop? – Connor Feb 19 '23 at 13:51
  • See https://stackoverflow.com/q/8385322/20017547 and https://man7.org/linux/man-pages/man3/strdup.3.html – Harith Feb 19 '23 at 16:43
  • Can you edit you answer to explain that `strdup` uses malloc? I'll happily accept it afterwards. – Connor Feb 19 '23 at 21:28
  • "the pointer address is correctly stored in my pointer array, but that nothing prevents it from pointing at nonsense? If the address isn't re-instantiated every time, why would all the addresses point to the same information?" -> An array name is a constant pointer to the first element of the array. You have a single array here, it's address is not overwritten each time though the loop, it's contents are. That being said, the contents become invalid after each iteration because the scope of the array ends. You can read `strdup()`'s man page, of if that's unclear, ask a separate question. – Harith Feb 20 '23 at 01:09
  • ..if you don't find an existing one. – Harith Feb 20 '23 at 01:11
  • I think this post is incomplete without a brief explanation of `malloc`, the stack, and the heap. Links to external resources would be just as useful. I also think that using `strdup` as a fix, without explaining why it works, doesn't fully answer the question. I'm grateful for your answer, and if you don't have the time, I'll add the extra information myself once the review queue has been reduced. – Connor Feb 20 '23 at 10:17
  • There are numerous questions about `malloc()`, the stack, and the heap already on stack overflow. I believe my answer is relevant to the question asked. If you believe it's incomplete, kindly provide an answer with all the relevant details and the manual pages. :) – Harith Feb 20 '23 at 10:41
1

Why doesn't passing an address to a pointer array store that unique value?

Question is amiss.
The same unique value address (of string[]) was stored successfully 3 times in string_array[x].

Is simply that after each iteration of the first for() block, that address was invalid.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • So, because I'm doing no other memory operations, the same address is assigned by the `char string[50];` call every time? – Connor Feb 19 '23 at 21:30
  • 1
    @Conner, yes, very likely the same pointer is assigned with each `string_array[x] = string;`. Even if it was not the same or not, it make little difference as the pointer value ceases to be valid when the block ends, which is the next line: `}`. – chux - Reinstate Monica Feb 19 '23 at 21:42
  • Okay, so does that mean that, technically, the pointer address is correctly stored in my pointer array, but that nothing prevents it from pointing at nonsense? If the address isn't re-instantiated every time, why would all the addresses point to the same information? – Connor Feb 19 '23 at 21:54
0

I slightly modified your code. Use strcpy to pass a string to another. Note that 50 is the maximum string size you can read because of char string[50].

#include <stdio.h>
#include <string.h>

int main() {

    char string_array[10][50];
    char string[50];

    for (int x = 0; x < 2; x++)
    {
        printf("Input Name of Fruit number %d\n", x);
        fgets(string, 50, stdin);
        strcpy(string_array[x], string);
    }

    for (int x = 0; x < 2; x++)
    {
        printf("%s\n", string_array[x]);
    }
    return 0;
}
  • 2
    Note that `string` and `strcpy` here is ultimately pointless. As `fgets` is never checked for failure, you may as well just `fgets(string{array[x], 50, stdin);` and skip the unnecessary `strcpy`. Better still, do that, but also rack how many strings have been successfully read based on the result of `fgets`, and limit the lifetime of both loops based on said-same. – WhozCraig Feb 19 '23 at 11:50
  • @WhozCraig I tried this reading the string directly into `string_array`, but, ultimately, that didn't work. – Connor Feb 19 '23 at 13:53
  • 2
    @Connor It attempting to use `fgets(string_array[x], 50, stdin);` did your code also change to `char string_array[10][50];` or still use the old `char *string_array[10];`? – chux - Reinstate Monica Feb 19 '23 at 15:32
  • @chux-ReinstateMonica Ahhhh, okay, sorry I didn't notice that change! Why does that change make a difference to how it gets stored? – Connor Feb 19 '23 at 21:29
  • 1
    @Connor, with `char *string_array[10];` from the question and `strcpy(string_array[x], string);` from this answer, what value is the pointer `string_array[x]` given to `strcpy()`? Where was that pointer initialized or assigned? – chux - Reinstate Monica Feb 19 '23 at 21:39
  • @chux-ReinstateMonica I don't know if this is a prompt or a question. But I think you're trying to make me realise that `char *string_array[10]` is never initialised, which is why the code fails. Am I correct? – Connor Feb 19 '23 at 21:42
  • 1
    @Connor Yes and yes. Even being initialized is not enough. Had `string_array[x] == NULL;` occurred before `strcpy(string_array[x], string);`, `string_array[x]` would have a nicely been assigned, yet no string storage should be attempted at `NULL`. `string_array[x]` needs to first point to memory large enough to save `string`. – chux - Reinstate Monica Feb 19 '23 at 21:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251991/discussion-between-connor-and-chux-reinstate-monica). – Connor Feb 19 '23 at 21:54