8

I am looking at some code I did not write and wanted help to understand an element of it. The code stores character arrays, creates pointers to these arrays (assigning the pointers the arrays addresses). It looks like it then creates an array to store these characters pointers addresses and I just wanted some clarification on what I am looking at exactly. I also am confused about the use of the double (**) when creating the array.

I have included a stripped down and simplified example of what I am looking at below.

char eLangAr[20] = "English";
char fLangAr[20] = "French";
char gLangAr[20] = "German";

char* eLangPtr = eLangAr;
char* fLangPtr = fLangAr;
char* gLangPtr = gLangAr;


char **langStrings [3]=
{
    &eLangPtr,
    &fLangPtr,
    &gLangPtr
};

When using the array they pass it as an argument to a function.

menu (*langStrings[0]);

So the idea is that the character array value "English" is passed to the function but I'm having trouble seeing how. They pass the menu function a copy of the value stored in the langStrings function at location 0, which would be the address of eLandPtr? If someone could explain the process in English so I could get my head around it that would be great. It might be just because it has been a long day but its just not straight in my head at all.

user1649972
  • 381
  • 3
  • 14
  • 2
    The array value of "English" is *not* passed to the function. A pointer to it is passed. `langStrings[0]` has the value `&eLangPtr`. Dereferenced, `*langStrings[0]` gives you what `&eLangPtr` poitns to, which is address of `eLangAr[20]`. The `**` is because `&eLangPtr` is the address of a `char` pointer. So `langStrings` is an array of pointers to char pointers, which is `char **`. – lurker Feb 12 '18 at 15:52
  • 4
    Whoever wrote this was being really damn convoluted. Those `LangPtr` variables aren't even necessary since arrays decay into pointers automatically. – hegel5000 Feb 12 '18 at 15:55
  • @hegel5000 Would you care to elaborate? – Killzone Kid Feb 12 '18 at 16:00
  • @Killzone Kid https://stackoverflow.com/questions/1461432/what-is-array-decaying – hegel5000 Feb 12 '18 at 16:00
  • @hegel5000 Thank you I know what array decaying into pointer is but still didn't understand your comment, hence asked politely if you can elaborate. – Killzone Kid Feb 12 '18 at 16:01
  • @Killzone Kid I'm pretty sure that `char **langStrings[3] = {eLangAr, fLangAr, gLangAr}` is gonna do the same thing as what they already have, right? – hegel5000 Feb 12 '18 at 16:03
  • 2
    @hegel5000 it doesn't compile https://ideone.com/YpbnEA – Killzone Kid Feb 12 '18 at 16:04
  • 2
    One time when arrays don't decay to pointers is when you take the address of an array. so `char **langStrings [3]= { &eLangAr, ...` is not the same as `char **langStrings [3]= { &eLangPtr, ...` – john Feb 12 '18 at 16:05
  • 1
    Mind you I must agree that the code is hopelessly and needlessly convoluted. – john Feb 12 '18 at 16:06
  • @Killzone Kid Ooops. It looks like it would have to be `char *langStrings[3]` for my expression to work (in which case langStrings would be of type char**). I still stand by my original point about whoever wrote the code OP is looking at being too convoluted, though :P Why the hell do they need char***? – hegel5000 Feb 12 '18 at 16:09
  • @hegel5000 No worries, just wanted to make sure I am not missing something – Killzone Kid Feb 12 '18 at 16:12
  • 2
    @hegel5000 clearly a three star programmer, http://wiki.c2.com/?ThreeStarProgrammer – john Feb 12 '18 at 16:15

4 Answers4

8

You are correct that langStrings contains pointers to pointers of array of characters. So each langString[i] points to a pointer. That pointer points to an array. That array contains the name of a language.

As others point out, it looks a bit clumsy. I'll elaborate:

char eLangAr[20] = "English"; is an array of 20 chars and the name "English" is copied to it. I don't expect that variable eLangAr will ever contain something else than this language name, so there is no need to use an array; a constant would be sufficient.

char **langStrings [3]= ... Here, it would be sufficient to have just one indirection (one *) as there seems no need to ever have the pointer point to anything else (randomly shuffle languages?).

in conclusion, just having the following should be sufficient:

const char *langStrings [3]=
{
    "English",
    "French",
    "German"
};

(Note the const as the strings are now read-only constants/literals.)


What the given code could be useful for is when these language names must be spelled differently in different languages. So "English", "French", "German" become "Engels", "Frans", "Duits". However, then there still is one level of indirection too many and the following would be sufficient:
char *langStrings [3]=
{
    aLangArr,
    fLangAr,
    gLangAr
};
Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
1

Ok, here goes. The **ptrToptr notation means a pointer to a pointer. The easiest way to think on that is as a 2D matrix - dereferencing one pointer resolves the whole matrix to just one line in the matrix. Dereferencing the second pointer after that will give one value in the matrix.

This declaration:

char eLangAr[20] = "English";

Declares an array of length 20, type char and it contains the characters 'E', 'n', 'g', 'l', 'i', 's', 'h' '\0' so it is (probably) null terminated but not full (there are some empty characters at the end). You can set a pointer to the start of it using:

char* englishPtr = &eLangAr[0]

And if you dereference englishPtr, it'll give the value 'E'. This pointer:

char* eLangPtr = eLangAr;

points to the array itself (not necessarily the first value).

If you look at

*langStrings[0]

You'll see it means the contents (the dereferencing *) of the pointer in langStrings[0]. langStrings[0] is the address of eLangPtr, so dereferencing it gives eLangPtr. And eLangPtr is the pointer to the array eLangAr (which contains "English").

I guess the function wants to be able to write to eLangAr, so make it point to a different word without overwriting "English" itself. It could just over-write the characters itself in memory, but I guess it wants to keep those words safe.

Pam
  • 1,146
  • 1
  • 14
  • 18
  • Pam, note that the `&` and `[0]` are not needed in the assignment. `char* englishPtr = eLangAr` would be enough. And yes, it will always point to the first character, so to `E`. – Paul Ogilvie Feb 13 '18 at 13:51
  • Thanks, my C is a little rusty these days. I may have been thinking about the pointers to pointers. – Pam Feb 13 '18 at 18:27
0

** represents pointer to pointer. It's used when you have to store pointer for another pointer.

Value of eLangPtr will be pointer to eLangAr which will has the value "English"

Yogeshwar Shendye
  • 49
  • 1
  • 1
  • 10
0

Here:

char eLangAr[20] = "English";

an array is created. It has capacity of 20 chars and contains 8 characters - word "English" and terminating NULL character. Since it's an array, it can be used in a context where pointer is expected - thanks to array-to-pointer decay, which will construct a pointer to the first element of an array. This is done here:

char* eLangPtr = eLangAr;

Which is the same as:

char* eLangPtr = &eLangAr[0]; // explicitly get the address of the first element

Now, while char* represents pointer to a character (which means it points to a single char), the char** represents a pointer to the char* pointer.

The difference:

char text[] = "Text";
char* chPointer = &ch[0];
char** chPointerPointer = &chPointer; // get the address of the pointer

std::cout << chPointer; // prints the address of 'text' array
std::cout << *chPointer; // prints the first character in 'text' array ('T')
std::cout << chPointerPointer; // prints the address of 'chPointer'
std::cout << *chPointerPointer; // prints the value of 'chPointer' which is the address of 'text' array
std::cout << *(*chPointerPointer); // prints the first character in 'text' array ('T')

As you can see, it is simply an additional level of indirection.

Pointers to pointers are used for the same reason "first-level" pointers are used - they allow you to take the address of a pointer and pass it to a function which may write something to it, modifying the content of the original pointer.

In this case it is not needed, this would be sufficient:

const char *langStrings [3]=
{
    eLangPtr,
    fLangPtr,
    gLangPtr
};

And then:

menu (langStrings[0]);
Mateusz Grzejek
  • 11,698
  • 3
  • 32
  • 49