0

So, here's my logic:

This is some text:

char *text;

Then this is array of texts:

char **arr;

Then array of these arrays is:

char ***arr2d;

And if I want a function to modify it, it needs to accept it as:

char ****arr2d;

And within the function use it as:

*arr2d = (e.g. allocate);

So if I want to create 2D array like this and make the first row, first column contain just a letter 'a', then why does this not work?

   #define COLUMNS 7

    void loadTable(char ****table)
    {
            *table = (char ***) malloc(sizeof(char**));

            if (!*table) {
                printf("Allocation error for table rows.");
                return;
            }

            *table[0] = (char**) malloc(COLUMNS * sizeof(char*));

            if (!*table[0]) {
                printf("Allocation error for table columns.");
                return;
            }

            *table[0][0] = (char*) malloc(2 * sizeof(char));

        *table[0][0][0] = (char)(97);
        *table[0][0][1] = '\0';
    }


    int main()
    {

        char ***table;

        loadTable(&table);

        return 0;
    }
Bondy
  • 15
  • 1
  • 6
  • 1
    The assumption that you need 4 pointers is not correct. True, to change memory parameters for an array of strings, you will need to pass the address of that memory block, but you will not need 4 asterisks to do that. – ryyker May 05 '17 at 17:03
  • @ryyker I got lost a bit. So in the main function I need to pass it by address, but the receiving function needs only 3 stars? – Bondy May 05 '17 at 17:13
  • Yes, that is true. See below for explanations. – ryyker May 05 '17 at 17:55
  • 1
    Although "Then this is array of texts:" may seem to apply to `char **arr;"`, `arr` is not an _array_. It is a pointer. It is a [pointer to a pointer of `char`](https://cdecl.org/?q=char+**arr). With `char *b[10]`, `b` is an [_array_](https://cdecl.org/?q=char+*b%5B10%5D). I have found keeping this difference clear aids is coping with coding problems such as posted here. – chux - Reinstate Monica May 05 '17 at 18:40

2 Answers2

2

You would need only 3 *** to do what you describe, not 4 ****. Be aware, there are methods to allow you to avoid excessive depth in terms of arrays of arrays of strings. And there are also good reasons to avoid excessively deep orders of arrays, not the least is the need to free(.) everything you allocate. That means exactly one call to free(.) for each and every call to [m][c][re]alloc(.).

But to address your question...

In general, to create new memory for a single array of a previously allocated memory, you could use a function prototyped as:

char * ReSizeBuffer(char **str, size_t origSize);

Where if say the previously allocated buffer were created as:

char *buf = calloc(origSize, 1);  

...the usage could look like:

char *tmp = {0};

tmp = ReSizeBuffer(&buf, newSize); //see implementation below
if(!tmp)
{
   free(buf);
   return NULL;
}
buf = tmp;
///use new buf
...

Then if this works for a single array of char, then the prototype for allocating new memory for a previously allocated array of strings might look like this:

char ** ReSizeBuffer(char ***str, size_t numArrays, size_t strLens);

Where if say the previously allocated 2D buffer were created as:

char **buf = Create2DStr(size_t numStrings, size_t maxStrLen); //see implementation below

...the usage could look like:

char **tmp = {0};

tmp = ReSizeBuffer(&buf, numStrings, maxStrLen);
if(!tmp)
{
   free(buf);
   return NULL;
}
buf = tmp;
///use new buf
...

Implementations:

Implementation of ReSizeBuffer. This must be expanded if you desire to implement the second prototype:

char * ReSizeBuffer(char **str, size_t size)
{
    char *tmp={0};

    if(!(*str)) return NULL;

    if(size == 0)
    {
        free(*str);
        return NULL;
    }

    tmp = (char *)realloc((char *)(*str), size);
    if(!tmp)
    {
        free(*str);
        return NULL;
    }
    *str = tmp;

    return *str;
}

Implementation of Create2DStr might look like this:

char ** Create2DStr(size_t numStrings, size_t maxStrLen)
{
    int i;
    char **a = {0};
    a = calloc(numStrings, sizeof(char *));
    for(i=0;i<numStrings; i++)
    {
      a[i] = calloc(maxStrLen + 1, 1);
    }
    return a;
}
ryyker
  • 22,849
  • 3
  • 43
  • 87
  • The only comment would be if there was a desire to preserve the original content of `buf` in the event of `realloc` failure, you could eliminate the `free`, depends on the situation. Also, there isn't a need for the `if { ... return NULL; } else { buf = tmp; ... }`. If failure occurs, you have already *returned*, so a simple `if { ... return NULL; } buf = tmp; ...` is sufficient. These are just nits, so good job. The `tmp = (char *)realloc...` is also the known [**Do I cast the result of malloc?**](http://stackoverflow.com/q/605845/995714) nit... – David C. Rankin May 05 '17 at 18:18
  • @DavidC.Rankin - Thanks for the detailed response! - regarding casting... Up until this year, when my environment was updated to it's latest revision (an implementation of ANSI C99), I never had any problem with not casting the return of [m][c][re]alloc(.). Now I get big nasty compiler warnings. I have not yet contacted the company that produces this compiler with a bug report, but this makes me wonder if they are letting some C++'esk behaviors into my precious `C99` system. – ryyker May 05 '17 at 18:24
  • @DavidC.Rankin - regarding the free'ing of the original buffer - true not _absolutely_ necessary. But I put it in because if a request is made by a calling function, its because more memory is needed. If that memory was not granted, then the caller is going to fail when the attempt to write more content to the same buf overflows, leading to access violation or UB. I would rather have the return be an explicit `NUL` to allow the caller the knowledge to avoid those fail modes. On the other point, I agree. I will simplify the statement. Thanks again. – ryyker May 05 '17 at 18:32
  • Any particular reason for non-C standard `ssize_t` instead of `size_t`? – chux - Reinstate Monica May 05 '17 at 18:45
  • @chux - Not a good one. It may have something to do with compiling for 64bit target in one of my applications, and needed to suppress a compiler warning. In any case, I have edited to `size_t`. – ryyker May 05 '17 at 18:59
  • Using `size_t i;` may reduce some of those warnings. – chux - Reinstate Monica May 05 '17 at 19:01
  • @chux - I do not understand. The warnings were _because_ I used `size_t`. Probably a mismatch warning because of an assignment with some other target size dependent variable in my code. Honestly, I cannot recall what drove me to use it. – ryyker May 05 '17 at 19:02
  • 1
    `size_t numStrings ... int i; ... i – chux - Reinstate Monica May 05 '17 at 19:06
  • 1
    @chux - That may have very well been the scenario producing the warnings. Thanks for clarifying. – ryyker May 05 '17 at 19:10
0

I just stumbled upon this old question of mine and spotted the problem immediately. Here is the answer:

The number of asterisks is actually correct, the problem is operator evaluation. Specifically, all lines of code of this form:

*table[0]

should have been written as:

(*table)[0]
Bondy
  • 15
  • 1
  • 6