0

I'm trying to understand how can I create a C program that declares an "array of strings" whose size is unknown at the time of declaration. This is what I've got so far:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int n, i;
  char** words;

    printf("How many strings you want to input? \n");
    scanf("%d", &n);

  words = malloc(sizeof(char*) * n);

  for (i = 0; i < n; i++) {
    printf("Input your %d string: ", i + 1);
    scanf("%s", words[i]);
  }

  for (i = 0; i < n; i++) {
    printf("%s\n", words[i]);
  }

  return 0;
}

The program compiles, but I get a Segmentation fault error.

Bobby Wan-Kenobi
  • 885
  • 9
  • 18
  • 5
    You allocated the `char *[]` array, now you need to allocate *each* of the strings that the user input, ie each `words[i]` needs to be allocated. How to you know the size... big mystery! Eith you allocate a unique big string and ensure the user doesn't input more than that, input in it, allocate `words[i]` with the now known size, and copy the big string into this new allocated area. – Déjà vu Aug 27 '20 at 10:10
  • 1
    Don't use `scanf()` without checking the return value... – DevSolar Aug 27 '20 at 13:17

2 Answers2

0

You only allocated memory for the pointer to the strings, but not for the strings themselves. Attempting to store strings at non-allocated memory invokes undefined behavior.

The pointer are just pointer. You cannot store strings in them. You need to reserve memory for where the pointer should point to.

#define STR_SIZE 100              // max length of a string (incl. null terminator)

printf("How many strings you want to input? \n");
if (scanf("%d", &n) != 1)
{
    fputs("Error at input", stderr);
    // further error routine.
}

// allocate memory for the pointer to the strings.
words = malloc(sizeof(*words) * n);      
if (!words)
{
    // error routine if the memory allocation failed.
    perror("malloc"); 
    exit(EXIT_FAILURE);    
}

// allocate memory for the strings themselves.
for (int i = 0; i < n; i++)
{
     words[i] = malloc(sizeof(**words) * STR_SIZE);   
     if (!words[i])
     {
         // error routine if the memory allocation failed.
         perror("malloc"); 
         exit(EXIT_FAILURE); 
     }
}

Side notes:

  • Always check the return of memory-management functions if an error happened at the allocation! Same goes for input operations such as scanf().

  • Note that using sizeof(*words) and sizeof(**words) instead of sizeof(char*) and sizeof(char) is more safe for the case your code changes.

  • Good answer, I had forgotten about checking for errors in memory allocation. What's the difference when using `sizeof(*words)` in the 1st allocation, and `sizeof(**words)` when allocating strings? Why is it more safe? I mean I could write *char, since all pointers have the same size no? – Bobby Wan-Kenobi Aug 27 '20 at 10:33
  • @BobbyWan-Kenobi [Here](https://stackoverflow.com/q/17258647/12139179) you get it explained in detail. – RobertS supports Monica Cellio Aug 27 '20 at 10:35
  • @BobbyWan-Kenobi No, pointer types do not need to have the same size. That's a common confusion. [Here](https://stackoverflow.com/a/63076594/12139179) is another answer of mine where I focus that and link to other well answers focusing that. – RobertS supports Monica Cellio Aug 27 '20 at 10:39
  • Thanks for the aclaration. Why in the first memory allocation are you not using `sizeof(**word)`` and in the second one `sizeof(*word)`? It seems that you have them swapped no? – Bobby Wan-Kenobi Aug 27 '20 at 10:50
  • @BobbyWan-Kenobi `words` is a pointer to pointer to `char` (type `char **`). If you dereference `words` (`*words`) you get a pointer to `char` (type `char *`). That's what we need at the first step as we need to allocate space for the pointers. If you double dereference `words` (`**words`) you get a `char`. That's what we need at the second step when allocating the memory for the actual strings. – RobertS supports Monica Cellio Aug 27 '20 at 10:57
  • Oooh, I see. The first step is clearer now, but I fail to understand the second though. Can I then use in the 2nd allocation: ``` words[i] = malloc(sizeof(char) * STR_SIZE); ``` After all, what I want is to allocate space for 100 characters. (I mean, I can use it, already did and got no error or warnings, but I would like your opinion on it) – Bobby Wan-Kenobi Aug 27 '20 at 13:05
  • @BobbyWan-Kenobi You can use or `words[i] = malloc(sizeof(char) * STR_SIZE); ` or `words[i] = malloc(sizeof(**words) * STR_SIZE); `. They both do the same in your case as dereferencing `words` twice gives you `char`: It's just a matter of coding style but the latter version keeps you more safe as already said and since you used this style already the first allocation, it would be better to stick to one style. An important part in the life as a coder. – RobertS supports Monica Cellio Aug 27 '20 at 13:12
  • @BobbyWan-Kenobi Sure, there is no problem allocating 100 characters instead for each string, I just took 50 as example. Edited it in the code now to 100. Your memory high-probably will be capable to allocate memory for it, I'm sure. You might only become problems at numbers like 100000 f.e. but this is irrelevant in this case. It's also dependent on how much strings you want to have but actually when only allocating a few strings this is a range it isn't worth to talk or worry about getting out of memory. – RobertS supports Monica Cellio Aug 27 '20 at 13:12
0

You did not allocate the memory properly for individual char pointer words[i].

This was ok - you allocated memory for the pointer to pointer to char words:

words = malloc(sizeof(char*) * n);

But then you haven't allocated for individual pointers of that words yet. To do so, you need to provision how much buffer to for individual word. As an example, if each word is 100 char, then:

for (i = 0; i < n; i++)
{
    printf("Input your %d string: ", i + 1);
    words[i] = malloc(sizeof(char) * 100);  //<--
    scanf("%s", words[i]);
}

Also remember to free the buffer once you're done.

for (int i=0; i<n; ++i) {
    free(words[i]);
}

free(words);
return 0;
artm
  • 17,291
  • 6
  • 38
  • 54
  • Good answer too, I wasn't freeing memory at all after I was done. Now I don't know which answer to choose, this or the one above... – Bobby Wan-Kenobi Aug 27 '20 at 10:34