Any of these. Note that this is one thing almost everyone gets confused about in C.
char ** a;
Here a is a pointer to one or more pointers to one or more characters. There are a bunch of pointers somewhere in memory and each one points to a string (so to speak). a refers to the first of these pointers. (a+1) refers to the 2nd one. The blocks of characters each reside somewhere in memory but not necessarily anywhere near each other. You might do this:
for (int i = 0 ; i < 10 ; ++i) {
a++ = malloc(100);
}
or you could do this:
char *b = malloc(1000);
for (int i = 0 ; i < 10 ; ++i) {
a++ = b;
b += 100;
}
And you will have filled in the array with 10 pointers to 100 characters each. (Well, really spaces for 100 characters since we didn't fill in any characters yet.)
char* a[];
Here, again, a is a bunch of pointers to the first of a bunch of characters. It's really the same as char ** a but makes more sense if you are going to reuse them all.
for (int i = 0 ; i < 10 ; ++i) {
a[i] = malloc(100);
}
which will do the same thing as above but leaves it easier to get back to any given block of characters. For example, we could do strcpy(a[4], "Bob was here");
char a[][];
This is a little different in that it is a pointer to a bunch of characters that can be broken up into blocks of the same length. You would typically give sizes in this case unless its too big to allocate on the stack. char a[10][100] would allocate 1000 character spots in memory with them divided into 10 groups of 100 each.
Notice that your example {"0a","0b","0c"}; is a way to allocate three blocks of memory. Each is three bytes long and is pre-populated with the characters there (and a closing \0 character.
char *v[K][L];, it's not really clear that we should skip the pointer in the first "round" and go directly to the second array.