Search code examples
carraysmultidimensional-arraydynamic-memory-allocation

How do I create an array of strings?


I need to create an array I where column array is a fixed size and the row array is dynamically allocated (using malloc).

I looked at other similar questions and they either make the entire thing fixed or dynamically allocated. How can you do both?

char A[malloc][100];

My guess would be:

char *A[100];

Solution

  • Following on from my comment, you can statically declare an array-of-pointers and then dynamically allocate storage for each line read from a file. Below is a short example showing this approach. You must keep an index or counter of the number of lines read so you can prevent writing beyond the end of your array of pointers.

    Note xcalloc below is just a wrapper for calloc that allocates space for each line and does error checking. Allocation below is the long-way (note: you can replace the long-way with strdup as indicated in the comments) The full allocate/validate/nul-terminate approach to allocation is shown below for example/explanation.

    You can change the number of pointers declared by changing the #define MAXL value and adjust the maximum characters per-line by changing #define MAXC value. Look over and understand what each part of each line is doing. Note the use of a static buffer buf into which the initial lines are read before memory is allocated and the contents assigned to an index in your array. This is very useful as it allows you to validate what you have read before allocating space for it and copying the sting to new memory. It allows you to skip empty lines, etc is a much simpler manner than having to free/unindex an unwanted line:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXC 256    /* max chars per-line   */
    #define MAXL 100    /* initial num pointers */
    
    void *xcalloc (size_t n, size_t s);
    
    int main (int argc, char **argv) {
    
        char *array[MAXL] = {NULL};
        char buf[MAXC] = {0};
        size_t i, idx = 0;
        FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
        if (!fp) {
            fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
            return 1;
        }
    
        while (fgets (buf, MAXC, fp))  /* read all lines from fp into array */
        {
            size_t len = strlen (buf);
    
            /* validate complete line read */
            if (len + 1 == MAXC && buf[len - 1] != '\n')
                fprintf (stderr, "warning: line[%zu] exceeded '%d' chars.\n",
                        idx, MAXC);
    
            /* strip trailing '\r', '\n' */
            while (len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
                buf[--len] = 0;
    
            /* allocate & copy buf to array[idx], nul-terminate
             * note: this can all be done with array[idx] = strdup (buf);
             */
            array[idx] = xcalloc (len + 1, sizeof **array);
            strncpy (array[idx], buf, len);
            array[idx++][len] = 0;
    
            /* MAXL limit check - if reached, break */
            if (idx == MAXL) break;
        }
        if (fp != stdin) fclose (fp);
    
        printf ("\n lines read from '%s'\n\n", argc > 1 ? argv[1] : "stdin");
        for (i = 0; i < idx; i++)
            printf ("   line[%3zu]  %s\n", i, array[i]);
    
        for (i = 0; i < idx; i++)
            free (array[i]);    /* free each line */
    
        return 0;
    }
    
    /* simple calloc with error checking */
    void *xcalloc (size_t n, size_t s)
    {
        void *memptr = calloc (n, s);
        if (memptr == 0) {
            fprintf (stderr, "xcalloc() error: virtual memory exhausted.\n");
            exit (EXIT_FAILURE);
        }
    
        return memptr;
    }
    

    Compile

    gcc -Wall -Wextra -O3 -o bin/fgets_lines_stat_dyn fgets_lines_stat_dyn.c
    

    Use/Output

    $ ./bin/fgets_lines_stat_dyn dat/captnjack.txt
    
     lines read from 'dat/captnjack.txt'
    
       line[  0]  This is a tale
       line[  1]  Of Captain Jack Sparrow
       line[  2]  A Pirate So Brave
       line[  3]  On the Seven Seas.
    

    Memory Leak/Error Check

    In any code your write that dynamically allocates memory, you have 2 responsibilites regarding any block of memory allocated: (1) always preserves a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory and to confirm that you have freed all the memory you have allocated. For Linux valgrind is the normal choice. There are so many subtle ways to misuse a block of memory that can cause real problems, there is no excuse not to do it. There are similar memory checkers for every platform. They are all simple to use. Just run your program through it.

    $ valgrind ./bin/fgets_lines_stat_dyn dat/captnjack.txt
    ==22770== Memcheck, a memory error detector
    ==22770== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
    ==22770== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
    ==22770== Command: ./bin/fgets_lines_dyn dat/captnjack.txt
    ==22770==
    
     lines read from 'dat/captnjack.txt'
    
       line[  0]  This is a tale
       line[  1]  Of Captain Jack Sparrow
       line[  2]  A Pirate So Brave
       line[  3]  On the Seven Seas.
    ==22770==
    ==22770== HEAP SUMMARY:
    ==22770==     in use at exit: 0 bytes in 0 blocks
    ==22770==   total heap usage: 6 allocs, 6 frees, 1,156 bytes allocated
    ==22770==
    ==22770== All heap blocks were freed -- no leaks are possible
    ==22770==
    ==22770== For counts of detected and suppressed errors, rerun with: -v
    ==22770== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)