简体   繁体   中英

How do 2D arrays in C become 1D arrays?

I would appreciate if someone could explain to me the following behavior:

Say I declare a static 2D array

float buffer[NX][NY];

Now, if I want to populate this array, I have notice that it could be done this way:

initarray(buffer, NX, NY);

#define INITDATAVAL 0.5

void initarray(void *ptr, int nx, int ny)
{
  int i, j;

  float *data = (float *) ptr;

  for (i=0; i < nx*ny; i++)
    {
      data[i] = INITDATAVAL;
    }
}

My question is, if buffer is a 2D array, how can it be used as a 1D array once it is passed to initarray function? I am struggling to understand it...

When 2D arrays are statically allocated, the memory allocated is contiguous, but could this way be used if buffer is dynamically allocated instead?

A 2D array with 3 x 4 elements (ie a matrix) looks like this in memory:

A1 A2 A3 A4 B1 B2 B3 B4 C1 C2 C3 C4

Since the underlying storage is continuous, one can simply convert the array to a pointer to the first element and access all elements using a single offset (this 'cast', which is called 'decaying' in such a context, happens automatically when buffer is passed to initarray ).

(In this sample, the compiler would translate an expression such as buffer[n][m] to buffer + n*NY+m Basically, 2D arrays are just a comfortable notation for 2D data stored in 1D arrays).

For a start, initarray should take a float* argument, not void* .

When you convert an array to a pointer, you lose type information about the dimension. You're really converting it to a pointer to the first element, and acknowledging that storage is contiguous.

char foo [2][2] = { {'a','b'}, {'c','d'} }; // Stored as 'a', 'b', 'c', 'd'

You can retain dimension information with templates.

template <int W, int H>
void initarray (float (&input)[W][H]) {
    for (int x = 0; x < W; ++x) {
        for (int y = 0; y < H; ++y) {
            input [x][y] = INITDATAVAL;
        }
    }
}

int main () {
    float array [3][4];
    initarray (array);
}

Here, input is a reference to an array of the given type (and dimensionality is part of the full type). Template argument deduction will instantiate an overload of initarray with W=3 , H=4 . Sorry for the jargon, but that's how it works.

Incidentally, you will not be able to call this version of initarray with a pointer argument, but you can provide overloads if you want. I often write things like this

extern "C" void process (const char * begin, const char * end);

template <typename N>
void process (const char * (&string_list) [N]) {
    process (string_list, string_list + N);
}

The idea is to provide the most-general possible interface, implement it once in a separate translation unit or library, or whatever, and then provide friendlier, safer interfaces.

const char * strings [] = {"foo", "bar"};
int main () {
    process (strings);
}

Now if I change strings , I don't have to change the code elsewhere. I also don't have to think about irritating details like whether I have maintained NUMBER_OF_STRINGS=2 correctly.

An array is a contiguous series of objects.

An array of arrays is also a contiguous series of objects, but these objects happen to be arrays, which are themselves just made up of their elements placed end-to-end in memory. Picture:

float a[2][3];
a[0]                      a[1]
+-------+-------+-------++-------+-------+-------+
|float  |float  |float  ||float  |float  |float  |
|a[0][0]|a[0][1]|a[0][2]||a[1][0]|a[1][1]|a[1][2]|
|       |       |       ||       |       |       |
+-------+-------+-------++-------+-------+-------+

As this is a series of cells in a row containing floats, it can also be viewed as a single array of 6 floats (if viewed through an appropriate pointer). New picture:

float* b(&a[0][0]);//The &a[0][0] here is not actually necessary
                   //(it could just be *a), but I think
                   //it makes it clearer.
+-------+-------+-------++-------+-------+-------+
|float  |float  |float  ||float  |float  |float  |
|*(b+0) |*(b+1) |*(b+2) ||*(b+3) |*(b+4) |*(b+5) |
|       |       |       ||       |       |       |
+-------+-------+-------++-------+-------+-------+
^       ^       ^        ^       ^       ^       
|       |       |        |       |       |       
b      b+1     b+2      b+3     b+4     b+5

As you can see, a[0][0] becomes b[0] , and a[1][0] becomes b[3] . The whole array can be seen as just a series of floats, and not a series of arrays of floats.

All the memory for the 2D array has been allocated contiguously.

This means that given a pointer to the start of the array, the array appears to be a large 1D array as each row in the 2D array follows the last.

The data is simply stored sequentially on disk. Like so:

0:              buffer[0][0],
1:              buffer[0][1],
.                ...
NY-2:           buffer[0][NY-2],
NY-1:           buffer[0][NY-1],
NY:             buffer[1][0],
NY+1:           buffer[1][1],
.                ...
NY*2-2:         buffer[1][NY-2],
NY*2-1:         buffer[1][NY-1],
.                ...
NY*(NX-1):      buffer[NX-1][0],
NY*(NX-1)+1:    buffer[NX-1][1],
.                ...
NY*(NX-1)+NY-2: buffer[NX-1][NY-2],
NY*(NX-1)+NY-1: buffer[NX-1][NY-1],

The array is essentially a pointer to the first element. So what you do in the for loop is sequentially fill data, while the data just as well could be interpreted as a single array containing the whole block of data ( float[] ) or as a pointer ( float* ).

Worth noting is that on some (old/peculiar) systems the data may be padded. But all x86 systems pad to 32-bit boundary (which is the size of a float) and compilers usually (at least MSVC) pack to 32-bit alignment, so it's usually ok to do this.

Partial answer to your edited question:

When 2D arrays are statically allocated, the memory allocated is contiguous, but could this way be used if buffer is dynamically allocated instead?

The reason you can treat a statically allocated 2D array as a 1D array is that the compiler knows the sizes of the dimensions so can allocate a contiguous block and then it calculates the index into that memory when you use the index operators as in buffer[x][y].

When you allocate memory dynamically you can choose to make it 1D or 2D, but you cannot treat it as both like you can with a statically allocated array, because the compiler will not know the size of your innermost dimension. So you can either:

  • Allocate an array of pointers and then for each of those allocate a 1D array. You can then use buffer[x][y] syntax.
  • Allocate a 1D array, but then you must manually calculate the index yourself in the from buffer[y * x_dim + x]

A 2D array is laid out contiguously in memory, so with the right type punning you can treat it as though it had been declared as a 1D array:

T a[N][M];
T *p = (&a[0][0]);

so

a[i][j] == p[i*N + j]

Except when it is the operand of the sizeof or unary & operators, or is a string literal being used to initialize an array in a declaration, an expression of type "N-element array of T " is converted to an expression of type "pointer to T ", and its value is the address of the first element of the array.

When you call

initarray(buffer, NX, NY);

the expression buffer is replaced with an expression of type "pointer to NY -element array of float ", or float (*)[NY] , and this expression is passed to initarray .

Now, the values of the expressions buffer and &buffer[0][0] are the same (the address of an array is the same as the address of the first element in the array), but the types are not ( float (*)[NY] as opposed to float * ). This matters in some contexts.

In C, you can assign void * values to other object pointer types and vice-versa without a cast; this is not true in C++. I'd be curious to see if g++ throws up any warnings about this.

If it were me, I'd pass the address of the first element of buffer explicitly:

initarray(&buffer[0][0], NX, NY);

and change the type of the first parameter from void * to float * , just to keep everything as direct as possible:

void initarray(float *data, int nx, int ny)
{
  ...
  data[i] = ...;
  ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM