1

I have an array of strings: {"foo", "bar", "baz"} and I want to convert it into an array array of string: {{"foo", "bar", "baz"}}, (part of a bigger project). My program will use args as an array of strings, so the input is: ./a.out foo bar baz. With 1 and 3 arguments it works nice, but with 2 arguments it gives me segmentation fault. My code:

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

char ***args2cmd(int argc, char **args){ 
  //Create array of array of strings
  char ***arr = malloc(sizeof(char ***));
  int tami = 0;
  int tamj = 0;
  //Arry of strings
  arr[tami] = malloc(sizeof(char **));
  //String
  arr[tami][tamj] = malloc(sizeof(char *));

  for(int i=0; i<argc; i++){  
    tamj++;
    arr = realloc(arr, tamj * sizeof(**arr));
    arr[tami][tamj-1] = args[i]; 
  }
  return arr;
}

int main(int argc, char **args) {
  char ***arr = args2cmd(argc, args);
  for(int i=0; arr[i]; i++){
    for(int j=0; arr[i][j]; j++){
      printf("%s ", arr[i][j]);
    }
    printf("\n");
  }

  return 0; 
}

1 Answers1

1

Bellow is a little code demonstration of how you could use an array of void pointers to accomplish your task. Code is fully commented with all relevant explanations. Compile with gcc -o astr astr.c.

Call with ./astr hello world from command line, for example.

/* astr.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

/* @n is the number of string you passed the function */
char **build_array(int n, ...)
{
    /* no error checking...
     * +1 because we terminate the array with a NULL
     */
    char **s = malloc((n + 1) * sizeof(*s));
    va_list ap;
    int i;

    va_start(ap, n);
    for(i = 0; i < n; i++) 
        s[i] = va_arg(ap, char *);
    va_end(ap);

    /* terminate the array with a NULL */
    s[i] = NULL;
    return s;
}

int main(int argc, char **argv)
{
    /* array of strings is just an array of pointers...
     *
     * Since you can play around with void points _by the standard_,
     * we can just make an array of voids to hold whatever pointers we
     * want, IFF we keep track of the types for the conversions.
     *
     * Say we do:
     *
     *  astr[0] = argv; // this is type (char **)
     *
     *  and them we do
     *
     *  float **foo = astr[0]; // VALID SYNTAX, but WRONG SEMANTIC
     *  
     *  The above is valid syntax since we can convert to and from
     *  void pointers, but we stored (char**) in astr[0], not
     *  (float**), and this will blow up in your face.
     */
    void **astr;

    /* alloc space for the array of strings.
     * no error cheking!
     */
    astr = malloc(3 * sizeof(*astr));

    /* the first element is the command line.
     * You could make a function to copy @argv into a new array
     */
    astr[0] = argv;

    /* just a helper function to build some more arrays */
    for(int i = 1; i < 3; i++)
        astr[i] = build_array(3, "hello", "world", "!!!");

    for(int i = 0; i < 3; i++) {
        char **s = astr[i];
        printf("Array #%d at addr %p\n", i, s);

        /* This assumption is dangerous! You should always KNOW 
         * the size of your arrays
         */
        for(int j = 0; s[j]; j++) 
            printf("s[%d] = %s\n", j, s[j]);
    }

    return 0;
}

After your comment:

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

char ***args2cmd(int argc, char **args)
{
    //Create array of array of strings
    // FIRST malloc()
    char ***arr = malloc(sizeof(char ***));

    int tami = 0;
    int tamj = 0;

    /* arr => 1 slot for 1 array
     */

    //Arry of strings
    // SECOND malloc
    arr[tami] = malloc((argc + 1) * sizeof(char **));

    /* arr[tami] = malloc(sizeof(char**)) gives us just 
     * arr[0] => 1 slot, which doesn't make sense.
     *
     * So we change it, because WE KNOW the size of the array! It's 
     * going to hold argc + 1 strings. Why the +1? Because you use the
     * trick of stoping loops based on a NULL element.
     *
     *
     * Keep in mind that @tami is bound the the amount of space pointed
     * to by arr! So, that's the first malloc() you did.
     *
     */
    //String
    //arr[tami][tamj] = malloc(sizeof(char *));
    /* arr[0][0] = 1 slot
     *
     * If this is a string, why only one slot?
     * You need more space for strings, ie:
     *  arr[tami][tamj] = malloc(strlen(something))
     *
     * So we comment this out, since it doesn't make any sense here.
     */

    int i; /* so we can use it later for the NULL */
    for (i = 0; i < argc; i++) {
        /* strdup() will alloc space and copy the string you give it
         * to this new region, then it will return your region. It's
         * similar to doing:
         *  char *s1 = "hello":
         *  char *s2 = malloc(strlen(s1));
         *  return strcpy(s2, s1);
         */
        arr[tami][i] = strdup(args[i]);
    }
    /* you assume the arrays end with NULL, so you
     * have to put it here
     */
    arr[tami][i] = NULL;
    return arr;
}

int main(int argc, char **args)
{
    char ***arr = args2cmd(argc, args);
    for (int i = 0; arr[i]; i++) {
        for (int j = 0; arr[i][j]; j++) {
            printf("%s ", arr[i][j]);
        }
        printf("\n");
    }

    return 0;
}
Enzo Ferber
  • 3,029
  • 1
  • 14
  • 24
  • My problem is the realloc, I just need to know how to do it correctly in the function I posted, this doesn't help too much, but thanks anyway! – Henrique Jesus May 22 '21 at 21:58
  • @HenriqueJesus Not "just" `realloc` that's wrong. The logic behind your function is wrong. Check the edited answer, your function now works as expected, returning an array that will yield the complete command line arguments as an array of array of strings. – Enzo Ferber May 23 '21 at 00:52