Create a pointer to two-dimensional array
Asked Answered
V

10

137

I need a pointer to a static 2-dimensional array. How is this done?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

I get all kinds of errors like:

  • warning: assignment from incompatible pointer type
  • subscripted value is neither array nor pointer
  • error: invalid use of flexible array member
Viccora answered 27/6, 2009 at 13:20 Comment(2)
@JohannesSchaub-litb That doesn't exist anymore. (How do I view it again...? I know low-rep members can view it, but I forgot how...)Frere
@muntoo: Here's a copy of it: gist.github.com/sharth/ede13c0502d5dd8d45bdEmblematize
J
160

Here you wanna make a pointer to the first element of the array

uint8_t (*matrix_ptr)[20] = l_matrix;

With typedef, this looks cleaner

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

Then you can enjoy life again :)

matrix_ptr[0][1] = ...;

Beware of the pointer/array world in C, much confusion is around this.


Edit

Reviewing some of the other answers here, because the comment fields are too short to do there. Multiple alternatives were proposed, but it wasn't shown how they behave. Here is how they do

uint8_t (*matrix_ptr)[][20] = l_matrix;

If you fix the error and add the address-of operator & like in the following snippet

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Then that one creates a pointer to an incomplete array type of elements of type array of 20 uint8_t. Because the pointer is to an array of arrays, you have to access it with

(*matrix_ptr)[0][1] = ...;

And because it's a pointer to an incomplete array, you cannot do as a shortcut

matrix_ptr[0][0][1] = ...;

Because indexing requires the element type's size to be known (indexing implies an addition of an integer to the pointer, so it won't work with incomplete types). Note that this only works in C, because T[] and T[N] are compatible types. C++ does not have a concept of compatible types, and so it will reject that code, because T[] and T[10] are different types.


The following alternative doesn't work at all, because the element type of the array, when you view it as a one-dimensional array, is not uint8_t, but uint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

The following is a good alternative

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

You access it with

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

It has the benefit that it preserves the outer dimension's size. So you can apply sizeof on it

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

There is one other answer that makes use of the fact that items in an array are contiguously stored

uint8_t *matrix_ptr = l_matrix[0];

Now, that formally only allows you to access the elements of the first element of the two dimensional array. That is, the following condition hold

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

You will notice it probably works up to 10*20-1, but if you throw on alias analysis and other aggressive optimizations, some compiler could make an assumption that may break that code. Having said that, i've never encountered a compiler that fails on it (but then again, i've not used that technique in real code), and even the C FAQ has that technique contained (with a warning about its UB'ness), and if you cannot change the array type, this is a last option to save you :)

Jarvis answered 27/6, 2009 at 13:37 Comment(8)
+1 - nice info on the int (*)[][20] breakdown - can't do that in C++Ragin
@litb, i'm sorry but this is wrong as your solution does not provide any allocation of storage for the array.Brittle
@Rob, i don't quite understand you. the storage in all these cases is provided by the array l_matix itself. The pointers to them take storage from where-ever they are declared in and as (stack, static data segment, ...).Jarvis
Just curious why do we need "&" address of l_matrix?Nne
uint8_t (*matrix_ptr)[20] = l_matrix;. This requires an additional 20 units on the stack? If all I need is just a reference to the original array is there no way to do this with a constant amount of memory? I assume that the original 2D array is stored contiguously.Jacobjacoba
@Jacobjacoba - no, that creates only one pointer. You may have confused it with uint8_t *d[20], which creates an array of 3 pointers to uint8_t, but that would not work in this case.Kerstinkerwin
Do you mean that the C standard doesn't allow to access multidimensional arrays with a one-dimensional pointer? This is complete news to me, and contradicts answers to other questions, like this for example: #36647786Justen
I just realized you are right. But I thought that this makes for another question, so I created a new one: #69785590Justen
F
42

To fully understand this, you must grasp the following concepts:

Arrays are not pointers!

First of all (And it's been preached enough), arrays are not pointers. Instead, in most uses, they 'decay' to the address to their first element, which can be assigned to a pointer:

int a[] = {1, 2, 3};

int *p = a; // p now points to a[0]

I assume it works this way so that the array's contents can be accessed without copying all of them. That's just a behavior of array types and is not meant to imply that they are same thing.



Multidimensional arrays

Multidimensional arrays are just a way to 'partition' memory in a way that the compiler/machine can understand and operate on.

For instance, int a[4][3][5] = an array containing 4*3*5 (60) 'chunks' of integer-sized memory.

The advantage over using int a[4][3][5] vs plain int b[60] is that they're now 'partitioned' (Easier to work with their 'chunks', if needed), and the program can now perform bound checking.

In fact, int a[4][3][5] is stored exactly like int b[60] in memory - The only difference is that the program now manages it as if they're separate entities of certain sizes (Specifically, four groups of three groups of five).

Keep in mind: Both int a[4][3][5] and int b[60] are the same in memory, and the only difference is how they're handled by the application/compiler

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

From this, you can clearly see that each "partition" is just an array that the program keeps track of.



Syntax

Now, arrays are syntactically different from pointers. Specifically, this means the compiler/machine will treat them differently. This may seem like a no brainer, but take a look at this:

int a[3][3];

printf("%p %p", a, a[0]);

The above example prints the same memory address twice, like this:

0x7eb5a3b4 0x7eb5a3b4

However, only one can be assigned to a pointer so directly:

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

Why can't a be assigned to a pointer but a[0] can?

This, simply, is a consequence of multidimensional arrays, and I'll explain why:

At the level of 'a', we still see that we have another 'dimension' to look forward to. At the level of 'a[0]', however, we're already in the top dimension, so as far as the program is concerned we're just looking at a normal array.

You may be asking:

Why does it matter if the array is multidimensional in regards to making a pointer for it?

It's best to think this way:

A 'decay' from a multidimensional array is not just an address, but an address with partition data (AKA it still understands that its underlying data is made of other arrays), which consists of boundaries set by the array beyond the first dimension.

This 'partition' logic cannot exist within a pointer unless we specify it:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

Otherwise, the meaning of the array's sorting properties are lost.

Also note the use of parenthesis around *p: int (*p)[5][95][8] - That's to specify that we're making a pointer with these bounds, not an array of pointers with these bounds: int *p[5][95][8]



Conclusion

Let's review:

  • Arrays decay to addresses if they have no other purpose in the used context
  • Multidimensional arrays are just arrays of arrays - Hence, the 'decayed' address will carry the burden of "I have sub dimensions"
  • Dimension data cannot exist in a pointer unless you give it to it.

In brief: multidimensional arrays decay to addresses that carry the ability to understand their contents.

Fourteen answered 10/1, 2016 at 6:21 Comment(4)
The first part of the answer is great, but the second is not. This is not correct: int *p1 = &(a[0]); // RIGHT !, actually it is identical to int *p1 = a;Marketable
@Marketable Thank you for spotting that error, I've corrected it. I cannot say for certain why the example which defines this "rule" also defied it. Worth restating is that just because two entities can be interpreted as pointers and yield the same value, does not mean they carry the same meaning.Fourteen
Only the compiler sees arrays and pointers differently. During runtime, arrays degenerate to nothing but constant pointers.Moldboard
> "First of all (And it's been preached enough), arrays are not pointers." - Sorry, while your answer is good this is totally unnecessary arrogance...Christenson
T
7

In

int *ptr= l_matrix[0];

you can access like

*p
*(p+1)
*(p+2)

after all 2 dimensional arrays are also stored as 1-d.

Tantalous answered 18/2, 2012 at 0:13 Comment(1)
*(p+k) is literally just p[k]!Moldboard
B
5

G'day,

The declaration

static uint8_t l_matrix[10][20];

has set aside storage for 10 rows of 20 unit8_t locations, i.e. 200 uint8_t sized locations, with each element being found by calculating 20 x row + column.

So doesn't

uint8_t (*matrix_ptr)[20] = l_matrix;

give you what you need and point to the column zero element of the first row of the array?

Edit: Thinking about this a bit further, isn't an array name, by definition, a pointer? That is, the name of an array is a synonym for the location of the first element, i.e. l_matrix[0][0]?

Edit2: As mentioned by others, the comment space is a bit too small for further discussion. Anyway:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

does not provide any allocation of storage for the array in question.

As mentioned above, and as defined by the standard, the statement:

static uint8_t l_matrix[10][20];

has set aside 200 sequential locations of type uint8_t.

Referring to l_matrix using statements of the form:

(*l_matrix + (20 * rowno) + colno)

will give you the contents of the colno'th element found in row rowno.

All pointer manipulations automatically take into account the size of the object pointed to. - K&R Section 5.4, p.103

This is also the case if any padding or byte alignment shifting is involved in the storage of the object at hand. The compiler will automatically adjust for these. By definition of the C ANSI standard.

HTH

cheers,

Brittle answered 27/6, 2009 at 13:38 Comment(1)
uint8_t (*matrix_ptr)[][20] << the first brackets must be left out, correct is uint8_t (*matrix_ptr)[20]Vesper
K
5

In C99 (supported by clang and gcc) there's an obscure syntax for passing multi-dimensional arrays to functions by reference:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

Unlike a plain pointer, this hints about array size, theoretically allowing compiler to warn about passing too-small array and spot obvious out of bounds access.

Sadly, it doesn't fix sizeof() and compilers don't seem to use that information yet, so it remains a curiosity.

Karmen answered 1/9, 2013 at 18:10 Comment(4)
This answer is misleading: That does not make the argument a fixed size array, it's still a pointer. static 10 is some kind of guarantee that at least 10 elements are present, which again means the size is not fixed.Hexa
@Hexa the question was about a pointer, so I don't see how answering with a pointer (noting by reference) is misleading. The array is fixed size from the perspective of the function, because access to elements beyond these bounds is undefined.Karmen
I don't think access beyond 10 is undefined, I can't see anything indicating that.Hexa
This answer seems to suggest that without the keyword static, the array would not be passed by reference, which is not true. Arrays are passed by reference anyway. The original question asked about a different use case - accessing elements of 2D array using a supplementary pointer within the same function/namespace.Kerstinkerwin
P
4

You can always avoid fiddling around with the compiler by declaring the array as linear and doing the (row,col) to array index calculation by yourself.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

this is what the compiler would have done anyway.

Prayerful answered 27/6, 2009 at 13:37 Comment(2)
This is what the C compiler does anyway. C doesn't really have any real notion of an "array"-- the [] notation is just syntactic sugar for pointer arithmeticSimonton
This solution has the disadvantage of never finding out the right way to do it.Rarebit
T
2

The basic syntax of initializing pointer that points to multidimentional array is

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

The the basic syntax for calling it is

(*pointer_name)[1st index][2nd index][...]

Here is a example:

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

int main() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

   char (*p)[5][100] = &balance; // Pointer initialization...

   printf("%s\n",(*p)[0]); // Calling...
   printf("%s\n",(*p)[1]); // Calling...

  return 0;
}

Output is:

Subham
Messi

It worked...

Tophus answered 25/3, 2018 at 6:21 Comment(0)
B
1

You can do it like this:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;
Badger answered 27/6, 2009 at 13:36 Comment(6)
doesnt this occupy 10 * 20 bytes of ram? (im on a microcontroller)Viccora
It will occupy 4 byte or whatever size a pointer is big in your box. But remember if you have this one, you have to index with matrix_ptr[0][x][y] or (*matrix_ptr)[x][y] . It's the direct and word-by-word interpretation of "pointer to two-dimensional array" :pJarvis
Thanks litb, I forgot to mention how to access it. There is no point to edit my answer since you did a great job with your answer :)Badger
So, does this occupy 10 * 20 bytes of RAM or not?Tessatessellate
@Danijel, since it's a pointer to two-dimensional array, it will occupy only 4 bytes or whatever size a pointer is in your box, i.e. 16-bits, 32-bits, 64-bits, etc.Badger
OK, thanks. Debuger confused me, shows some memory usage.Tessatessellate
S
1

You want a pointer to the first element, so;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}
Simonton answered 27/6, 2009 at 13:40 Comment(0)
S
0

You could also add an offset if you want to use negative indexes:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

If your compiler gives an error or warning you could use:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;
Siloa answered 29/1, 2015 at 9:58 Comment(1)
Hello. This question is tagged with c, so the answer should be in the same language. Please observe the tags.Marketable

© 2022 - 2024 — McMap. All rights reserved.