3

I am trying to call a function that takes char** as a parameter. Its job is to fill an array of strings (i.e. an array of char*). I know the max length of the strings, and I can pass the max number to fill as another parameter, so I was hoping to stack allocate it like this:

fill_my_strings(char** arr_str, int max_str); // function prototype

char fill_these[max_strings][max_chars_per_string]; // allocating chars
fill_my_strings(fill_these, max_strings); // please fill them!

Of course, I get the "cannot convert char[max_strings][max_chars_per_string] to char**" error.

I know this is some subtle (or not-so-subtle) problem with my understanding of the difference between arrays and pointers. I'm just not sure why it's not possible to pass this block of memory to something wanting a char** and have it fill in my stack-allocated chars. Could somebody please explain if this is possible, or if not, why not?

Is it possible to call a function like this without calling malloc / new?

aardvarkk
  • 14,955
  • 7
  • 67
  • 96
  • You can pass `char[][]` to functions expecting `char**`, but if you're trying to give them pointers on the stack, once the function ends those pointers will be pointing to invalid locations. You will need to allocate memory on the heap. – wkl Dec 04 '11 at 02:36
  • So I just do (in c++) a reinterpret_cast and everything should be fine? Because that doesn't work... – aardvarkk Dec 04 '11 at 02:37
  • 1
    ¤ `fill_my_strings` expects an array of pointers that each point to an array. So that's what you have to give it. You can just declare a `std::vector v`, use a loop to `push_back` the pointers (where each pointer points to the relevant part of your matrix), and then pass `&v[0]`. Cheers & hth., – Cheers and hth. - Alf Dec 04 '11 at 02:39
  • @aardvarkk You should say which language are you programming with. – Roman Byshko Dec 04 '11 at 02:40
  • @AlfP.Steinbach Why don't you put your comments as answers? :) – Roman Byshko Dec 04 '11 at 02:41
  • @RomanB: Since both C++ and C are tagged, I just assume C++. If a C++ solution is not acceptable, then the OP should not have used C++ tag. It's that simple, I think. – Cheers and hth. - Alf Dec 04 '11 at 02:42
  • @Roman B. Yeah -- I guess I don't really care which language I see the solution in (C or C++). I just wanted to know what the best way to do it is. – aardvarkk Dec 04 '11 at 02:42
  • 1
    @AlfP.Steinbach Yes, your answer is acceptable. My only concern with it is that historically I don't believe vector memory is actually *guaranteed* to be contiguous. I've read some entries about it before and it seems they basically always are, but it seems like poor style to assume as such by passing the pointer to the vector like that... I do like your solution though, and there are no explicit heap calls so that makes it pretty :) – aardvarkk Dec 04 '11 at 02:43
  • 2
    @RomanB: Re "Why don't you put your comments as answers?", well the SO community, via its self-appointed and its elected leaders, have made it clear that any answers from me including a valediction such as "cheers & hth.," at the end, are very unwanted. During the previous week I was suspended for a week due to my insistence on the "Cheers & hth.,", even though that decision was reversed within an hour or so. Also, one of my (correct) answers was deleted for being too short. Anyway, by posting as comment I avoid the whole hassling thing, until they also start deleting comments... Cheers, – Cheers and hth. - Alf Dec 04 '11 at 02:46
  • @aardvarkk: `vector` was always meant to have a guaranteed contiguous buffer, but by an oversight it was not required to have that in C++98. This was corrected in C++03 (i.e., C++03 guarantees it). We had to wait until C++11 to get the same for `basic_string`. Cheers, – Cheers and hth. - Alf Dec 04 '11 at 02:48
  • @aardvark: contiguity is guaranteed by C++03. It wasn't guaranteed by C++98, but that was basically an oversight. Nobody ever actually wanted to make their vectors non-contiguous, and AFAIK nobody ever has. It's not poor style to write to C++03. OK, so there are places where MSVC implements C++98 behavior instead of C++03 behavior, but there are rather more places where MSVC is straight-up non-conforming. And MSVC has contiguous vectors. So it's a non-issue other than to those interested in the history of the language. – Steve Jessop Dec 04 '11 at 02:49
  • 1
    @AlfP.Steinbach Thanks to both Steve and yourself for clarifying that point. It seems there's probably no better solution than Alf's in that case! If you wish Alf, feel free to post your solution as an actual answer. I may see if anybody else has any genius entries, but I will likely accept yours as the solution if that's not the case. Thanks! – aardvarkk Dec 04 '11 at 02:54
  • @AlfP.Steinbach I would add *reserve()* to your solution. – Roman Byshko Dec 04 '11 at 03:14

4 Answers4

3

The simple answer to your question is no; a two dimensional array is different than a pointer-to pointer type. Arrays decay to pointers to their first element, but pointers actually are that value.

The difference between these types is clear, if you cast both to char*

int x;
char *arr_pp[] = {"foo", "bar", "baz"};
char arr_2d[][4] = {"foo", "bar", "baz"};

char *cp = (char*)arr_pp;
for(x=0; x<3; x++)
     printf("%d ", cp[x]);
printf("\n");

cp = (char*)arr_2d;  
for(x=0; x<3; x++)
     printf("%d ", cp[x]);
printf("\n");

The output (on my computer) is:

-80 -123 4 
102 111 111 

Where the first row is gibberish formed by the fact that I'm printing an address cast into bytes, and the second row is the ascii values of "foo".

In a function taking a char ** the compiler can't know to decay array types, which don't actually contain pointers.

Dave
  • 10,964
  • 3
  • 32
  • 54
2

I am not sure why fill_my_strings() need a char** parameter. From your example, caller have already allocated the memory from stack. So using a char* should be OK.

But if you want to use char** or you can't modify the fill_my_strings() function, try following example code:

void fill_my_strings(char** arr_str, int max_chars_per_string, int max_strings)
{

    for(int i = 0; i < max_strings; ++i)
    {
        //Make sure you have enough space
        memcpy(*arr_str, "ABCD", sizeof("ABCD"));

        *arr_str += max_chars_per_string;
    }
}

char fill_these[max_strings][max_chars_per_string];
char* pointer = (char*)fill_these;
fill_my_strings(&pointer, max_strings, max_chars_per_string);
competent_tech
  • 44,465
  • 11
  • 90
  • 113
Shawnone
  • 850
  • 5
  • 17
2

Suppose you have n pointers to strings of m-1 maximum characters (m characters including the NULL). So, in pure C: sizeof(char[n][m]) will return n*m. sizeof(char**) will return the size of a pointer in your architecture, probably 32 (if x86) or 64 (if x86_64).

char[n][m] actually allocates the n*m byte contiguously. char** allocates a single pointer. This pointer references a memory stripe of *n bytes. Each of these n pointers points to a memory stripe of m characters.

So, considering that sizeof(char) == u, if you declare char a[n][m], when you use a[i][j], the compiler understands *(a + i*m*u + j*u). So, considering that sizeof(char *) == w, if you declare char **a, when you use a[i][j], the compiler understands ((a + i*w) + j*w).

Completely different data management.

The closes thing you could do to handle your special case is to create a char** variable, and populate it with the addresses of your stack allocated matrix.

char **tmp = malloc(max_strings * sizeof(char *));
int i;
for(i = 0; i < max_strings; i++){
    tmp[i] = &(fill_these[i][0]); //you probably can't reference a char[][] with a single index - not confirmed
}
Spidey
  • 2,508
  • 2
  • 28
  • 38
0

The obvious thing to do is build an index

In c use something like:

char string_list[num_strings][str_length];

// ...

char**index = calloc( (num_strings+1), sizeof(*index) ); // calloc insures NULL termination
for (int i=0; i<num_strings; ++i) {
   index[i] = string_list[i]
}

In c++ prefer new[] to calloc;

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
  • 2
    Why not not explicitly null-terminate; then you don't waste time initializing everything, just to overwrite most of it. Does new zero-fill? – Dave Dec 04 '11 at 02:44
  • @Dave: I like the array to be in a "safe" condition as soon as I get it. If you profiler tells you this is slowing you down you can replace it with `malloc` and `index[num_strings]=NULL;` – dmckee --- ex-moderator kitten Dec 04 '11 at 02:48
  • @Roman: I overwrite all but the *last* element because people often process `char**` type structures with `for(p=index; *p; ++p)`. NULL terminating the array makes that safe. If you can guarantee that this won't happen you don't need it. – dmckee --- ex-moderator kitten Dec 04 '11 at 02:49
  • @dmckee sorry, I deleted my comment because Dave said the same. So the reason is precaution, I see. – Roman Byshko Dec 04 '11 at 02:53
  • It appears that `new` wouldn't zero-fill the array; you *cannot* prefer `new` to `calloc`. – Dave Dec 04 '11 at 03:13
  • @Dave: Yes, I should have specified `new[]` to insure that [the constructor gets called for the pointers](http://stackoverflow.com/q/936999/2509). – dmckee --- ex-moderator kitten Dec 04 '11 at 03:18
  • I can't believe this correct answer is languishing down here. A pointer that takes `char **` is expecting a pointer to the first `char *` in an array of the same; a `char [][]` is an array of arrays of chars; so to pass the latter to the former you must create an array of `char *` values to pass. – caf Dec 04 '11 at 12:47