5

I understood multi-dimensional arrays as pointers to pointers, but perhaps I am wrong?

For example, I though:

char * var = char var[]

char ** var = char* var[] or char var[][]

char *** var = char var[][][] or char* var[][] or char** var[]

Is this incorrect? I was confused because I saw a char*[][] cast as a char** in a simple text book example.

I pasted the example below. Can anyone clear this up for me? Thanks!


/* A simple dictionary. */
#include <stdio.h>
#include <string.h>
#include <ctype.h>

/* list of words and meanings */

char  *dic[][40] = {
    "atlas", "A volume of maps.",
    "car", "A motorized vehicle.",
    "telephone", "A communication device.",
    "airplane", "A flying machine.",
    "", ""  /* null terminate the list */
};

int main(void)
{
    char word[80], ch;
    char **p;

do {
    puts("\nEnter word: ");
    scanf("%s", word);
    p = (char **)dic;
    /* find matching word and print its meaning */
    do {
        if(!strcmp(*p, word)) {
            puts("Meaning:");
            puts(*(p+1));
            break;
            }

        if(!strcmp(*p, word)) break;

        p = p + 2;  /* advance through the list */
        } while(*p);

    if(!*p) puts("Word not in dictionary.");
    printf("Another? (y/n): ");
    scanf(" %c%*c", &ch);

    } while(toupper(ch) != 'N');

return 0;

}
Russel
  • 3,369
  • 7
  • 28
  • 32
  • 1
    Note that in this specific case, in C++ you'd be better off using a `std::map` rather than what you have here. – Billy ONeal Oct 13 '10 at 16:57
  • the above piece of code is from **Complete reference in C by Herbert Schildt** page number _212_ but I dont get the use of the line **if(!strcmp(*p, word)) break;** i think that statement is redundant. Because if the word matches with the current element at *p then it shall have already caused the inner do while to have broken. please enlighten me regarding this issue – Abhishek Ghosh Feb 01 '19 at 17:15

5 Answers5

11

The rule for C is as follows:

6.3.2.1 Lvalues, arrays, and function designators
...
3 Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

The language for C++ is a little different:

4.2 Array-to-pointer conversion [conv.array]

1 An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to an rvalue of type “pointer to T”. The result is a pointer to the first element of the array.
...
8.3.4 Arrays [dcl.array]
...
7 A consistent rule is followed for multidimensional arrays. If E is an n-dimensional array of rank i × j × ... × k, then E appearing in an expression is converted to a pointer to an (n−1)-dimensional array with rank j × ... × k. If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n−1)-dimensional array, which itself is immediately converted into a pointer.

So the following all hold true:

Declaration        Expression        Type             Decays to
-----------        ----------        ----             ---------
     T a[N]                 a        T [N]            T *
                           &a        T (*)[N]     
                           *a        T
                         a[i]        T

  T a[M][N]                 a        T [M][N]         T (*)[N]
                           &a        T (*)[M][N]  
                           *a        T [N]            T *
                         a[i]        T [N]            T *
                        &a[i]        T (*)[N]      
                        *a[i]        T
                      a[i][j]        T

T a[M][N][O]                a        T [M][N][O]      T (*)[M][N]
                           &a        T (*)[M][N][O]
                           *a        T [M][N]         T (*)[N]
                         a[i]        T [M][N]         T (*)[N]
                        &a[i]        T (*)[M][N]  
                        *a[i]        T [N]            T *
                      a[i][j]        T [N]            T *
                     &a[i][j]        T (*)[N]
                     *a[i][j]        T
                   a[i][j][k]        T

The pattern should be clear for higher-dimensional arrays.

So let's analyze your dictionary:

/* list of words and meanings */         

char  *dic[][40] = {         
    "atlas", "A volume of maps.",         
    "car", "A motorized vehicle.",         
    "telephone", "A communication device.",         
    "airplane", "A flying machine.",         
    "", ""  /* null terminate the list */         
};

This isn't going to set up your dictionary the way you want; you've basically set this up as a 1-element array of 40 pointers to char. If you want an array of pairs of strings, then the declaration should look like this:

char *dic[][2] = 
{
  {"atlas", "A volume of maps"},
  {"car", "A motorized vehicle"},
  {"telephone", "A communication device"},
  {"airplane" , "A flying machine"},
  {NULL, NULL} // empty strings and NULLs are different things.  
}; 

The type of dic is "5-element array of 2-element arrays of pointer to char", or char *[5][2]. Going by the rules above, the expression dic should decay to char *(*)[2] -- a pointer to a 2-element array of pointer to char.

A function to search this dictionary would then look like this:

char *definition(char *term, char *(*dictionary)[2]) // *NOT* char ***dictionary
{
  while ((*dictionary)[0] != NULL && strcmp((*dictionary)[0], term) != 0)
    dictionary++;
  return (*dictionary)[1];
}

and you would call it from your main function like

char *def = definition(term, dic);

Note that we have to use parentheses around the *dictionary expression in the function. The array subscript operator [] has higher precedence than the dereference operator *, and we don't want to subscript into dictionary directly, we want to subscript into the array that dictionary points to.

John Bode
  • 106,204
  • 16
  • 103
  • 178
6

I understood multi-dimensional arrays as pointers to pointers, but perhaps I am wrong?

Yes, you are wrong. There is a difference between an array and a pointer. An array can decay into a pointer, but a pointer doesn't carry state about the size or configuration of the array to which it points. Don't confuse this automatic decay with the idea that arrays and pointers are the same -- they are not.

A char ** is a pointer to a memory block containing character pointers, which themselves point to memory blocks of characters. A char [][] is a single memory block which contains characters.

If you have a char ** and access it using ptr[x][y], the compiler changes that into *(*(ptr + x)+y). If you have a char [][], the compiler changes arr[x][y] into *(ptr + rowLength*y + x). (Note: I'm not 110% positive on the order of Xs and Ys here, but that doesn't matter for the point I'm making here) Note that given the pointer, the compiler doesn't know anything about the size or dimensions of the array, and cannot determine the actual address if you treat the pointer as a multidimensional array.

char *dic[][40] is an array of arrays of size forty, which contain character pointers. Therefore it doesn't match your assignment there at all.

p = (char **)dic; <-- This is why casts are bad. The compiler was telling you that what you really wanted to do to dic didn't make any sense. But since you can cast a pointer to any other pointer, the cast succeeds, even though trying to read the data that way will result in undefined behavior.

Billy ONeal
  • 97,781
  • 45
  • 291
  • 525
6

You need to refer to 'right left rule'. Alternatively you can deciper most of the C-ish declarations at here

So,

char *p[2][3] is parsed as

p is an array of 2 elements where each element is an array of 3 elements, such that each element is a pointer to a character.([] binds stronger than *)

char (*p)[2][3] is parsed as

"p is a pointer to a 2 element char array where each element is a char array of 3 elements." (parenthesis binds the strongest)

Chubsdad
  • 23,089
  • 4
  • 57
  • 108
2

Not looked in too much detail but I think the author is relying upon c laying out a 2d string array like this:

key, value, key, value, key, value in contiguous memory. Then traversing this array as a 1d array of strings p = (char **)dic;

This is one of the beauties and potential problems with C - it has lots of low level power but the you have protect yourself with decent code to prevent side effects.

Preet Sangha
  • 61,126
  • 17
  • 134
  • 202
  • That is exactly what he was going for. I believe the snippet is from C: The Complete Reference. That book seems to have a lot of unclear and bad C practices in it. Although the example in the book probably does work on most platforms I'd avoid such abuse of internal C mechanisms in general. KISS – in70x Apr 26 '15 at 03:13
2

One of my memorization rules for combinations of * and [] is the signature of main. Works! :-)

Your dic is an array of 40-element arrays, each element of which is a pointer to char.

#include <iostream>
#include <typeinfo>
using namespace std;

template< class Type, unsigned N >
void tellMeAbout( Type const (&)[N] )
{
    cout << "Well, it's an array of " << typeid( Type ).name() << ".\n";
}

int main()
{
    char  *dic[][40]    = { 0 };
    tellMeAbout( dic );
}

Using Visual C++ I get ...

Well, it's an array of char * [40].

Cheers & hth.,

– Alf

Cheers and hth. - Alf
  • 135,616
  • 15
  • 192
  • 304
  • That code is not valid. `typeid` is only required to be valid on polymorphic types. `char *[40]` is not polymorphic. Just because it works in MSVC doesn't mean it's standards compliant. Also, the question is tagged with both C and C++, but the OP's code is C, not C++, so it probably would have been better to post a C solution. – Billy ONeal Oct 13 '10 at 04:37
  • @Billy: Happily the code is valid. In C++0x N3092 the result of `typeid` applied to non-polymorphic type is defined by §5.2.8/3. Regarding the language used to inspect a type, C does not provide that facility, but C++ does. The above is standard C++. I mentioned Visual C++ because the output depends on the compiler. Cheers & hth – Cheers and hth. - Alf Oct 13 '10 at 04:42
  • @Alf: C++0x is not yet a standard. – Billy ONeal Oct 13 '10 at 04:56
  • @Billy: It's in about the same place in C++98. Argh, you force me to mount an unreliable USB-drive! OK, it's defined by C++98 §5.2.8/3. Huh, talk about coincidence! :-) By the way, Billy, was it you who scored my answer down? Then how about scoring it up again? ;-) – Cheers and hth. - Alf Oct 13 '10 at 05:01
  • @Alf: It's absolutely **not** valid in C++98. At least, `std::type_info::name` isn't required to be anything sensible. – Billy ONeal Oct 13 '10 at 05:04
  • 1
    @Billy: Here's the C++98 §5.2.8/3 wording that is incompatible with your idea: "When `typeid` is applied to an expression an expression other than an lvalue of polymorphic class type, the result refers to a `type_info` object representing the static type of the expression." The wording that directly covers the usage in the above code is in C++98 §5.2.8/4, "When `typeid` is applied to a *type-id*, the result refers to a `type_info` object representing the type of the *type-id*." Note that a restriction to polymorphic type would conflict with §5.2.8/3. Cheers & hth., – Cheers and hth. - Alf Oct 13 '10 at 05:15
  • THe problem is that the array notation has a different meaning if it's in a function argument list. a local variable `char *arg[10];` is a different type than the `arg` in `void func(char *arg[10]);` – nos Oct 13 '10 at 16:57
  • @nos: yes, I should have mentioned that. And it's a bit more general than that. In both C and C++ the array type decays to pointer to first element in any context where a pointer is required, e.g. writing `a+0`. In C++ also function declarations decay to pointers when used as formal argument type (I don't know whether C supports that syntax). And in C++ you can avoid the array-to-pointer type decay for a formal argument by passing an array by reference, as in the `tellMeAbout` :-) formal argument above. Cheers, – Cheers and hth. - Alf Oct 13 '10 at 17:22