2012-02-02 21 views
5

Le agradecería si alguien podría explicarme el siguiente comportamiento:¿Cómo las matrices 2D en C se convierten en matrices 1D?

decir te declarar una matriz 2D estática

float buffer[NX][NY]; 

Ahora, si quiero llenar esta matriz, no tengo noticia de que podría ser hace de esta manera:

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; 
    } 
} 

Mi pregunta es, si tampón es una matriz 2D, ¿cómo puede ser utilizado como una matriz 1D, una vez que se pasa a la función initarray? Estoy luchando por comprenderlo ...

Cuando las matrices 2D están asignadas estáticamente, la memoria asignada es contigua, pero ¿podría utilizarse de esta manera si se asigna dinámicamente buffer?

Respuesta

6

A 2D con 3 x 4 elementos (es decir, una matriz) se parece a esto en la memoria:

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

Dado que el almacenamiento subyacente es continua, uno puede simplemente convertir la matriz a un puntero al primer elemento y acceda a todos los elementos usando un solo desplazamiento (este 'molde', que se llama 'descomposición' en dicho contexto, se produce automáticamente cuando buffer se pasa al initarray).

(En esta muestra, el compilador traduciría una expresión como buffer[n][m] a buffer + n*NY+m Básicamente, las matrices 2D son solo una notación cómoda para datos 2D almacenados en matrices 1D).

4

Para empezar, initarray debe tomar un argumento float*, no void*.

Cuando convierte una matriz en un puntero, pierde información de tipo sobre la dimensión. Realmente lo está convirtiendo en un puntero al primer elemento y reconoce que el almacenamiento es contiguo.

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

Puede retener la información de las dimensiones con las plantillas.

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); 
} 

Aquí, input es una referencia a una matriz del tipo dado (y dimensionalidad es parte del tipo completo). La deducción del argumento de plantilla creará una instancia de una sobrecarga de initarray con W=3, H=4. Perdón por la jerga, pero así es como funciona.

Por cierto, no podrá llamar a esta versión de initarray con un argumento de puntero, pero puede proporcionar sobrecargas si lo desea. A menudo escribo cosas como esta

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); 
} 

La idea es proporcionar la interfaz más general posible, aplicar una vez en una unidad separada traducción o biblioteca, o lo que sea, y luego proporcionar, interfaces más amigables más seguras.

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

Ahora si cambio strings, que no tienen que cambiar el código en otro lugar. Yo también no tiene que pensar sobre detalles irritantes como si he mantenido NUMBER_OF_STRINGS=2 correctamente.

array
+0

+1 Para retener información con plantillas. Ni siquiera sabía sobre eso. – evotopid

1

Toda la memoria de la matriz 2D se ha asignado contiguamente.

Esto significa que dado un puntero al inicio de la matriz, la matriz parece ser una gran matriz 1D ya que cada fila en la matriz 2D sigue la última.

1

Los datos simplemente se almacenan secuencialmente en el disco. Como tal:

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], 

La matriz es esencialmente un puntero al primer elemento. Entonces lo que haces en el ciclo for es datos de relleno secuencialmente, mientras que los datos también podrían interpretarse como una única matriz que contiene todo el bloque de datos (float[]) o como un puntero (float*).

Vale la pena señalar que en algunos sistemas (antiguos/peculiares) los datos pueden ser acolchados. Pero todos los sistemas x86 rellenan el límite de 32 bits (que es del tamaño de un flotador) y los compiladores normalmente (al menos MSVC) se ajustan a la alineación de 32 bits, por lo que generalmente está bien hacerlo.

4

Una matriz es una serie contigua de objetos.

Una matriz de matrices también es una serie contigua de objetos, pero estos objetos son matrices, que a su vez están formadas por sus elementos colocados de extremo a extremo en la memoria. Foto:

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]| 
|  |  |  ||  |  |  | 
+-------+-------+-------++-------+-------+-------+ 

Como se trata de una serie de celdas de una fila que contiene flotadores, también se puede ver como una única matriz de 6 flotadores (si ve a través de un puntero apropiado). Nueva foto:

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 

Como se puede ver, se convierte en a[0][0]b[0], y se convierte en a[1][0]b[3]. Toda la matriz se puede ver como una simple serie de flotadores, y no como una serie de conjuntos de flotadores.

1

respuesta parcial a su pregunta editado:

Cuando matrices 2D se asignan estáticamente, la memoria asignada es contigua, pero de esta manera se podría utilizar si búfer se asigna dinámicamente en su lugar?

La razón se puede tratar de una matriz 2D estáticamente como una matriz 1D es que el compilador sabe que los tamaños de las dimensiones por lo que puede asignar un bloque contiguo y luego se calcula el índice en que la memoria cuando se utiliza el índice operadores como en buffer [x] [y].

Cuando asigna dinámicamente memoria puede elegir hacerla 1D o 2D, pero no puede tratarla como ambas cosas con una matriz estáticamente asignada, porque el compilador no sabrá el tamaño de su dimensión más interna. Entonces puede:

  • Asigne una matriz de punteros y luego, para cada uno de ellos, asigne una matriz 1D. Luego puede usar la sintaxis buffer [x] [y].
  • asignar una matriz de 1D, pero entonces debe calcular manualmente el índice de sí mismo en el registro de almacenamiento temporal [y * x_dim + x]
+0

lo tengo. ¡Gracias! – Manolete

1
gama

Un 2D se presenta de forma contigua en la memoria, por lo que con el tipo de juego de palabras la derecha se puede tratar como si que había sido declarado como una matriz 1D:

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

por lo

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

Ex excepto cuando es el operando de los operadores sizeof o unario &, o es un literal de cadena que se utiliza para inicializar una matriz en una declaración, una expresión de tipo "matriz de elementos N de T" se convierte en una expresión de tipo "puntero" a T ", y su valor es la dirección del primer elemento de la matriz.

Cuando se llama a

initarray(buffer, NX, NY); 

la expresión buffer se sustituye con una expresión de tipo "puntero a NY gama -elemento de float", o float (*)[NY], y esta expresión se pasa a initarray.

Ahora, los valores de las expresiones buffer y &buffer[0][0] son los mismos (la dirección de una matriz es el mismo que la dirección del primer elemento de la matriz), pero los tipos no (float (*)[NY] son en oposición a float *). Esto es importante en algunos contextos.

En C, puede asignar void * valores a otros tipos de puntero de objeto y viceversa sin un molde; esto es no verdadero en C++. Me gustaría saber si g ++ arroja alguna advertencia sobre esto.

Si fuera yo, me lo pase la dirección del primer elemento de amortiguación de forma explícita:

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

y cambiar el tipo del primer parámetro desde void * a float *, sólo para mantener todo lo más directa posible:

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

so '& buffer [0] [0]' equivalente a solo 'buffer'? – Manolete

+0

En términos de valores, sí. En términos de tipos, no. –

+0

Por qué es importante: no hay garantía de que un puntero a 'float' y un puntero a una matriz de' float' tengan el mismo tamaño o representación (muy poco probable, pero no imposible). Pasar 'buffer' a una función que espera' float * 'como un parámetro puede causar problemas en el tiempo de ejecución si los tamaños del puntero o las representaciones no son las mismas. Probablemente no estés trabajando en un sistema donde eso sería un problema, pero es bueno ser consciente de ello de todos modos. –