-1

I'm trying to pass the address of a 2D array to a function in C. I initialize the 2D array as:

const int N = 3;
char t[N][N];

I try to convert this into a char***:

char*** t_ptr = &t;

But it fails with a:

warning: initialization from incompatible pointer type

The function I want to receive the array has a prototype such as:

void f(char*** t, int N) { ... };

What am I doing wrong? Thank you.

rica01
  • 69
  • 2
  • 6
  • What does the function do with `t`? – user3386109 Oct 24 '18 at 03:26
  • Why a `char ***`? This is not the type of a pointer to a 2d array. – ad absurdum Oct 24 '18 at 03:33
  • I'm sure you are aware you created a 2D *Variable Length Array* rather than a 2D array with automatic storage type. `const int N = 3;` does not create a literal integer constant. For that you either need `#define N 3` or a global `enum { N = 3 };` – David C. Rankin Oct 24 '18 at 05:01
  • Arrays and pointers are not the exact same thing, first you should understand the differences between them and then you can enjoy with them. – pierDipi Oct 24 '18 at 05:31

3 Answers3

1

This

char*** t_ptr = &t;

is wrong as compiler pointed because t is an two dimensional array, instead of triple pointer like char*** use pointer to an array to point to it. For e.g

char t[3][3] = { "ab","de","gh"}; /* total 3 1D array & each 1D array has 3 elements */
char (*t_ptr)[3] = t; /* t_ptr is a pointer to an array, pointing to 3 elements at a time */

And you can print t_ptr like

for(int index = 0; index < 3 ; index++) {
        printf("%s\n",t_ptr[index]);
}
Achal
  • 11,821
  • 2
  • 15
  • 37
  • I am not familiar with this syntax: char (*t_ptr)[3] What does it mean @Achal ? – rica01 Oct 24 '18 at 07:03
  • @rica01 this is called "pointer to an array". You can read [here](https://stackoverflow.com/questions/6474104/difference-between-pointer-to-pointer-and-pointer-to-array) to know more. – Achal Oct 24 '18 at 08:20
  • [this](https://www.tutorialspoint.com/cprogramming/c_pointer_to_an_array.htm) may helps. – Achal Oct 24 '18 at 08:28
0

For reading c declarations, you can visit: https://cdecl.org/

Now using @Achal's example array:


#include <stdio.h>
#include <stdlib.h>

int main()
{
    // t is a 2D array (not a VLA)

    char t[3][3] =
    {
        {'a', 'b', '\0'},
        {'d', 'e', '\0'},
        {'g', 'h', '\0'}
    };
    printf("t is composed of 3 arrays of 3 characters each,\n");
    printf("with the following addresses (on my machine):\n");
    printf("--------------------------------------------------\n");
    printf("%p, %p, %p\n", t[0], t[1], t[2]);


// ------------------------------------------------
    // p is an array of 3 char pointers or char*
    // Notice initialization
    char* p[3] = {t[0], t[1], t[2]};
    // following line of code will break, if no '\0' is encountered
    // since %s requires a char* and a null terminator

    printf("\nPrint strings using p:\n");
    printf("------------------------\n");
    printf("%s, %s, %s\n", *p, *(p + 1), *(p + 2));

// ------------------------------------------------
    // q is a pointer to a char* or q has type char**
    // Notice initialization of q (q points to p)

    char** q = p;
    printf("\nUsing q:\n");
    printf("-----------\n");
    printf("%s, %s, %s\n", *q, *(q + 1), *(q + 2));

// ---------------- printing characters individually
    printf("\nIndividually:\n");
    printf("---------------\n");
    for(int i = 0; i < 2; i++)
    {
        for(int j = 0; j < 2; j++)
        {
            printf("%c ",
                  *(*(q + i) + j)
                   );
        }
        printf("\n");
    }
// -----------------------------------------------
    // r is a pointer to an array of size 3 containing char
    // r advances three char at a time (r doesn't know the size of t)

    char (*r)[3] = t;   // this is the type of t
    printf("\nUsing r:\n");
    printf("---------------\n");
    printf("%p, %p, %p\n", *r, *(r + 1), *(r + 2));

    // to extract chars
    printf("%c, %c", *(*(r + 0) + 0), *(*(r + 2) + 1));
// -----------------------------------------------

    return EXIT_SUCCESS;
}

Output:

t is composed of 3 arrays of 3 characters each,
with the following addresses (on my machine):
--------------------------------------------------
000000000022FE2F, 000000000022FE32, 000000000022FE35

Print strings using p:
------------------------
ab, de, gh

Using q:
-----------
ab, de, gh

Individually:
---------------
a b
d e

Using r:
---------------
000000000022FE2F, 000000000022FE32, 000000000022FE35
a, h
Process returned 0 (0x0)   execution time : -0.000 s
Press any key to continue.

  • You shouldn't build up pointer tables like `char* p[3] = {t[0], t[1], t[2]};` etc unless you have good reasons though. They are needlessly slow, as they lead to inefficient cache use. – Lundin Oct 24 '18 at 11:54
  • I have learned somethng @Lundin. Thanks. I should create a new post and ask you for further elaboration. Specifically, since this is done at compile time, why would it be slow? Excuse the lack of an embedded perspective on my part. –  Oct 24 '18 at 13:39
  • This isn't so much for embedded systems, as for high-end mainstream CPUs with lots of data cache: x86/x64, Cortex-A, PowerPC etc. The slow part isn't the initialization but variable access through multiple pointers, where each pointer points at different segments. See [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays) where I discuss arrays vs pointer-based look-up tables. – Lundin Oct 24 '18 at 13:47
-2
char t[N][N];

is in fact the same as

char t[N * N];

in memory. A pointer to such an array would in both cases be of type char *.

char *** is a pointer to a pointer, that is a pointer to a char, whereas char * is a pointer to a char and that's how you pass array references in C: You pass them as a pointer to the first element of that array and this first element is a char.

C cannot retain the exact type or structure of an array as soon as you pass it to functions. In memory, a char array is just a bunch of memory filled with chars and all you can pass around is a pointer to that memory. If that memory is char [] or char [][] or even char [][][] plays no role, in memory all three are a block full of chars and the function would have to explicitly know the structure in memory, otherwise all char arrays will always be char [] for a function.

I strongly discourage C beginners to ever use multidimensional arrays. Instead of

char t[N][N];
char c = t[y1][x1];
t[y2][x2] = 'X';

use

char t[N];
char c = t[y1 * N + x1];
t[y2 * N + x2] = 'X';

As that's basically what the compiler will internally do anyway.

Note that multidimensional arrays in C are not x-y, but y-x, the first value is the row, the second on the column, please see this tutorial.

Whoever disbelieves what I just said, try out this code:

int main ( ) {
    char a[5][5];
    for (int y = 0; y < 5; y++) {
        for (int x = 0; x < 5; x++) {
            a[y][x] = x + 10 * y;
        }
    }

    for (int y = 0; y < 5; y++) {
        for (int x = 0; x < 5; x++) {
            printf("%02d ", a[y][x]);
        }
        printf("\n");
    }

    printf("------\n");

    char * a2 = (char *)a;
    for (int y = 0; y < 5; y++) {
        for (int x = 0; x < 5; x++) {
            printf("%02d ", a2[y * 5 + x]);
        }
        printf("\n");
    }
}

You can run it online, if you like, the output is identical. Also have a look at the assembly code the compiler generates for either loop (e.g. gcc -S) and you will see it's almost identical as even in the first case the compiler uses an add and a mul instruction to access the correct memory location within the array.

Mecki
  • 125,244
  • 33
  • 244
  • 253
  • Given `char t1[N * N];` and `char t2[N][N];`, `t1` will decay to a `char *` in most expressions, but `t2` will decay to a `char (*)[N]`. This would happen in a function call if `t1` or `t2` were given as an argument, and happens because the first element of `t1[]` is a `char`, while the first element of `t2[][]` is _not_ a `char`, but a `char[N]`, i.e., an array of N `char`s. – ad absurdum Oct 24 '18 at 13:06
  • @DavidBowling No. `char[][]` is not the same as `char * []`. The 1st one is just a 2D array of char values and the 2nd one is an array of char pointers. And C is not C++, albeit a subset of it but with different behavior. In C you can only pass an array as pointer to the array storage. – Mecki Oct 24 '18 at 13:29
  • _You said in your answer_ that: "`char [N][N]` is in fact the same as `char [N * N]`". This is wrong. `char (*)[N]` is a pointer to an array of N `char`, which is a type in C (no idea where you got C++ from). This is relevant in function calls, e.g. with `void foo(size_t n, char arr[n][n]);` the function `foo()` will be expecting `char (*)[N]`, not `char *`. In fact, the type of the argument in the declaration is _modified_ to `char (*)[N]` by the compiler. I said nothing about `char *[]`, which is an array of pointers. – ad absurdum Oct 24 '18 at 13:47
  • Sorry, I see the "in memory" that follows. The issue is that a pointer to the `char [N][N]` is not a `char *`. There is a pointer to a 2d array: `char (*)[][]`, or a pointer to the first element of a 2d array: `char (*)[]`. The second is what is expected when an array identifier decays to a pointer to its first element. – ad absurdum Oct 24 '18 at 13:51
  • @DavidBowling http://tpcg.io/NO6YLS In memory both are a block of 25 bytes and accessing them with two indices is syntactical sugar. – Mecki Oct 24 '18 at 15:26