Punteros y matrices

18 de mayo 2009 Publicado en C / C + +

En el primer post de esta serie, hablamos un poco acerca de los punteros . En el segundo, se habla de referencias . Hoy se discute la relación íntima (ui!) Entre apuntadores y arreglos (o matrices).

Recordando matrices

Una matriz, o matriz, o de una estructura, es una abstracción matemática utilizada para representar un conjunto de datos homogéneos, es decir, del mismo tipo (int, float, etc.) Esta abstracción está organizada en formato de tabla, con filas y columnas. Cada elemento de la matriz ha únicos coordenadas (fila y columna), de modo que un elemento dado E (i, j) representa el único elemento en la línea "i", "la columna j".

La sintaxis para declarar matrices es el siguiente:

  / / Una dimensiones matrices o vectores
 10 ] ; // 10 elementos, do 0 ao 9 Ivet int [10] / / 10 elementos, desde 0 hasta 9
 23 ] ; // 23 elementos, do 0 ao 22 Cvet char [23] / / 23 elementos, desde 0 hasta 22

 / / Dos matrices unidimensionales
 2 ] [ 3 ] ; // 2 linhas (0 a 1) e 3 colunas (0 a 2) IMAT int [2] [3] / / 2 líneas (0-1) y tres columnas (0-2)
 10 ] [ 2 ] ; // 10 linhas (0 a 9) e 2 colunas (0 a 1) DMAT doble [10] [2] / / 10 líneas (0-9) y dos columnas (0-1) 

Cada elemento de la matriz es independiente de los demás y se puede acceder en la siguiente sintaxis:

  10 ] ; Ivet int [10];
 3 ] [ 4 ] ; IMAT int [3] [4];

 / / Cambiar el cuarto elemento del vector Ivet.
 / / Recuerde que usted comienza a contar desde el elemento cero
 ] = 13 ; Ivet [3] = 13;

 / / Leer el segundo elemento del vector Ivet
 ivet [ 1 ] ; int num = Ivet [1];

 / / Cambiar el elemento de la primera fila, segunda columna IMAT
 ] [ 1 ] = 42 ; IMAT [0] [1] = 42;

 / / Lectura del elemento en la tercera fila, cuarta columna del IMAT
 imat [ 2 ] [ 3 ] ; int foo = IMAT [2] [3]; 

En este post no vamos a discutir el fondo de la matriz, sólo investigar la relación entre matrices y punteros de una manera bastante intuitiva.

Los tamaños de las matrices

Dado que una matriz es una abstracción que contiene varios valores del mismo tipo, ¿cómo es de grande? ¿Cuánto espacio ocupa en la memoria?

Considere el siguiente código:

  17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
  10 ] ; Ivet int [10];
 13 ] ; char [cvet 13];
 20 ] ; [Doble ETVD 20];

 3 ] [ 4 ] ; CMAT Char [3] [4]
 5 ] [ 4 ] ; IMAT int [5] [4];

 "sizeof(int)     = " << sizeof ( int ) << endl ; Cout <<"sizeof (int) =" <<sizeof (int) <<endl;
 "sizeof(char)    = " << sizeof ( char ) << endl ; Cout <<"sizeof (char) =" <<sizeof (char) <<endl;
 "sizeof(double)  = " << sizeof ( double ) << endl ; Cout <<"sizeof (double) =" <<sizeof (double) <<endl;

 "sizeof(ivet)    = " << sizeof ( ivet ) << endl ; Cout <<"sizeof (Ivet) =" <<sizeof (Ivet) <<endl;
 "sizeof(cvet)    = " << sizeof ( cvet ) << endl ; Cout <<"sizeof (cvet) =" <<sizeof (cvet) <<endl;
 "sizeof(dvet)    = " << sizeof ( dvet ) << endl ; Cout <<"sizeof (ETVD) =" <<sizeof (ETVD) <<endl;
 "sizeof(cmat)    = " << sizeof ( cmat ) << endl ; Cout <<"sizeof (CMAT) =" <<sizeof (CMAT) <<endl;
 "sizeof(imat)    = " << sizeof ( imat ) << endl ; Cout <<"sizeof (IMAT) =" <<sizeof (IMAT) <<endl; 

El resultado es bastante razonable. El espacio ocupado por una matriz es igual al número de elementos multiplicado por el tamaño del tipo de elemento (filas x columnas x sizeof (tipo)). Sin embargo, si cada elemento es independiente, se supone que cada uno ocupa un lugar aparte en la memoria, de lo contrario uno se sobreponen a otro elemento. Por lo tanto, es que cada elemento tiene su propia dirección de memoria?

Arrays y direcciones de memoria

Por conveniencia, a continuación, se analizarán las direcciones posibles una gran variedad de personajes, cuyo tamaño sólo se da un byte:

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
  5 ; const int max = 5;
 max ] = { 'A' , 'B' , 'C' , 'D' , 'E' } ; Cvet char] [max = ('A', 'B', 'C', 'D', 'E');

 / / Mostrar el contenido, los valores de datos y direcciones.
 "Índice \t Valor \t Endereço do elemento \n " ) ; printf ("Valor índice t \ t \ dirección de elemento \ n");
 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%d \t %c \t %p \n " , i, cvet [ i ] , & cvet [ i ] ) ; printf ("% d \ t% c \ t \ p"% n, i, cvet [i], y cvet [i]);
 )

 / / Mostrar la dirección de la matriz
 "Endereço da matriz: %p \n " , & cvet ) ; printf ("Dirección de la matriz: p \ n%", y cvet);

 / / Mostrar la dirección de la matriz de nuevo
 "Endereço da matriz: %p \n " , cvet ) ; printf ("Dirección de la matriz: p \ n%", cvet); 

Las direcciones de los elementos son secuenciales, es decir, cada elemento es almacenado junto a la antigua. Además, hay otros dos hechos interesantes:

  1. La dirección de la matriz (y cvet), que se muestra en la línea 25, es el mismo que el primer elemento de la matriz;
  2. El muy variable cvet puede ser interpretado como un puntero, como se muestra en la línea 28;

En C + +, una matriz común es un bloque contiguo de memoria cuyo nombre se puede interpretar (emitidos) como un puntero que apunta a su primer elemento. Además, es válido para hacer un punto puntero a una matriz en donde el puntero hasta el destino es el mismo tipo que el tipo de los elementos del arreglo. Durante atrubuição de una matriz a un puntero, el compilador realiza una conversión de tipos implícita. El puntero de destino, debe interpretarse como un puntero al área de memoria ocupada por la matriz.

Una de las consecuencias no tan evidente es que durante la conversión implícita se pierde la información de que la zona era un arreglo de memoria. Así se pierde la información sobre el tamaño de la matriz. Desde el punto de vista del puntero, se señala al principio de un bloque arbitrario de la memoria, un tamaño demasiado arbitrario. Salir y entrar en una serie a un puntero significa ir de una abstracción más restrictivas y más abstracción de alto nivel a una vida menos restrictivo y más bajo nivel.

Por otro lado intenta asignar un puntero a una matriz genera un error de compilación de tipos incompatibles. Una matriz es un bloque de memoria de n datos (bytes), ya que un puntero tiene sólo un dato, una dirección. El compilador no puede saber de antemano si un puntero apunta a un área de 1, 2 o 200 bytes.

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
  300 ; const int max = 300;
 max ] ; Cvet char max] [;
 pc = 0 ; char * pc = 0;

 " \n Antes da atribuição \n " ) ; printf ("\ n Antes de la \ n cesión");
 "cvet = %p \n " , cvet ) ; printf ("cvet p \ n% =", cvet);
 "pc   = %p \n " , pc ) ; printf ("= p \ n% pc", del Código Penal);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (cvet) = \ n% lu", sizeof (cvet));
 "sizeof(pc)   = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (pc) = \ n% lu", sizeof (pc));

 pc = cvet;

 " \n Depois da atribuição \n " ) ; printf ("\ n Después de la \ n cesión");
 "cvet = %p \n " , cvet ) ; printf ("cvet p \ n% =", cvet);
 "pc   = %p \n " , pc ) ; printf ("= p \ n% pc", del Código Penal);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (cvet) = \ n% lu", sizeof (cvet));
 "sizeof(pc)   = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (pc) =% lu \ n", sizeof (pc)); 

Tenga en cuenta que antes de la asignación (línea 25) el pc puntero. es nula, desde que arrancó así. Ya tamaños indican que la matriz tiene 300 bytes y el puntero sólo 8 (mi maquina es una amd 64). Después de la asignación de ambos pasan el "punto" en la misma zona de memoria, pero los tamaños no cambian. Hubo una conversión implícita a char [300] a char * y el puntero en ese juego de la PC no se puede saber el tamaño del área de memoria a la que apunta. Pero la matriz cvet aún sabiendo exactamente lo que está fuera de la crisis existencial.

La aritmética de punteros - Vulgo, ¿y qué?

¿Por qué, si sé que los datos de una matriz están dispuestos uno junto al otro, puedo utilizar un puntero que saltará a la siguiente dirección y acceder al siguiente elemento. El nombre de este es punteros aritméticos.

  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
  6 ; const int max = 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; Cvet char] [max = ('B', 'L', 'A', 'B', 'O', 'S');

 pc = cvet ; char * pc = cvet;

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * ( pc + i ) ) ; printf ("% c", * (pc + i));
 )
 " \n " ) ; printf ("\ n");

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * pc ++ ) ; printf ("% c", * PC + +);
 )
 " \n " ) ; printf ("\ n");


 / / Ahora, con números enteros
 max ] = { 1 , 2 , 3 , 4 , 5 , 6 } ; Ivet int [max] = (1, 2, 3, 4, 5, 6);
 pi = ivet ; int * pi = Ivet;

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%p = %d \n " , pi, * pi ++ ) ; printf ("% p =% d n" \, pi pi * + +);
 )
 " \n " ) ; printf ("\ n");

 / / En dos dimensiones
 2 ] [ 3 ] = { { 'B' , 'L' , 'A' } , { 'B' , 'O' , 'S' } } ; CMAT char [2] [3] = (('B', 'L', 'A'), ('B', 'O', 'S'));
 ppc ; Char * ppc;

 char * ) cmat ; ppc = (char *) CMAT;

 int i = 0 ; i < 2 ; i ++ ) { for (int i = 0; i <2; i + +) (
     int j = 0 ; j < 3 ; j ++ ) { for (int j = 0, j <3 j + +) (
         "%c" , * ( ppc + 3 * i + j ) ) ; printf ("% c", * (ppc + 3 * i + j));
     )
     " \n " ) ; printf ("\ n");
 )
 " \n " ) ; printf ("\ n"); 

En la línea 18 de la pc puntero pasa a ser punto a la matriz cvet, y en consecuencia a su primer elemento, el carácter 'B'. En la línea 21 el contenido de PC, que es la dirección donde se guardaba el carácter 'B', es decir, se incrementó en de-referencia. En la primera pasada el valor de i es cero, por lo que es el valor de eliminar la referencia "B". En los pasos siguientes, la siguiente dirección se está de-hace referencia a otros caracteres almacenados en la matriz original. Es más o menos lo que el compilador hace internamente cuando se utiliza la sintaxis cvet [i]. La abstracción de la matriz que da una agradable manera más de tratar con áreas contiguas de la memoria que * (pc + i).

Pero si la abstracción de la matriz es simple de usar que la aritmética de punteros?

Una respuesta es la línea 26. Ella hace lo mismo que la línea 21, pero un poco más rápido. En la sintaxis de la línea 21, o de forma similar, la sintaxis de matrices, el acceso a cualquier dato puede ser más o menos resumió muy bien en los comandos:

  1. Tome la dirección de base de la matriz;
  2. Añadir a la dirección de el valor del índice;
  3. De las referencias de la nueva dirección;

Ya con la aritmética de punteros es similar a esto:

  1. De las referencias esta dirección;

El incremento de comandos (o cuenta que se hacen con las direcciones) No se lo diré porque es parte del ciclo, a pesar de i + +; es más rápido que a + b = c;. Ahora imagine esta pequeña ganancia de 66% aplicado a un área de datos de 1 MB. Habrá más de 2 millones comandos de menos!

La técnica de usar un puntero para manejar un área arbitraria de la memoria se utiliza generalmente en la programación de bajo nivel (más cerca de la máquina), manipulación de buffers y cuerdas, entre otros trucos sucios. En las entrañas de los ordenadores, las operaciones que las zonas de gran barrido de la memoria, con frecuencia se realizan con aritmética de punteros. En este nivel, Darwin reina y sólo los más preparados sobrevivirán. A partir de aquí la lengua empieza a dar un poder que sólo los puros de corazón pueden comprender.

Una nota importante es que entre las líneas 31 y 38 la experiencia se repite con números enteros. Tenga en cuenta que los números enteros de 4 bytes, los incrementos se ponen automáticamente a 4 en 4 bytes, no 1 en 1, es decir, el incremento se calcula automáticamente para sizeof (tipo). El incremento significa un puntero para acceder a la siguiente área de memoria similar a los datos actuales, no sólo la dirección siguiente. A medida que el tamaño de un char es un byte, cuando incremetamos un puntero a char, se mueven sólo un byte. Si incrementar un puntero al doble, vamos a pasar de 8 bytes, y así sucesivamente.

Otra observación es que una matriz de dos dimensiones puede ser "linealizado" como se muestra en las líneas 40-52. Esto es útil, en su caso, para hacer un mejor uso de la caché del procesador, por ejemplo.

void *, el pansexual de punteros

Antes he dicho que sólo era posible asignar una matriz a un puntero que era para el mismo tipo que los datos de matriz. Yo descaradamente mintiendo! La razón es que para alguien que renunció al cargo antes de este tema, es más seguro para creer que no puede :) !

Hay dos excepciones a la regla. La primera es cuando hay una conversión de tipos explícita y del destino del puntero "piensa" que apunta a la clase correcta. Un ejemplo está en la línea 44 del código anterior.

El segundo es el caso de los punteros a void. Un puntero a void es un puntero que no exige el tipo de datos que están en el área de la memoria a la que apunta. Él es un puntero a un área genérica de la memoria, algo muy bajo nivel.

Para utilizar una base de datos a la que apunta un puntero al vacío antes de hacer referencia a él-, debemos hacer una conversión explícita de un tipo válido, porque si un int * es de una referencia a un int y char * es de una referencia a char, adivinar lo que es una referencia de un void *?

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
  6 ; const int max = 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; Cvet char] [max = ('B', 'L', 'A', 'B', 'O', 'S');

 pv = cvet ; void * pv = cvet;

 / / Error de compilación.
 / / * Pv
 / / ¿Cuánto es sizeof (void)?

 int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) (
     "%c" , * ( ( ( char * ) pv ) + i ) ) ; printf ("% c", * (((char *) pv) + i));
 )
 " \n " ) ; printf ("\ n"); 

Puntero al vacío se utilizan cuando se necesita para que apunte a un área general de la memoria sin tener el control / conocimiento del tipo de datos que contiene este ámbito, o funciones que no pueden hacer suposiciones sobre los tipos de sus parámetros, como la API lib pthreads (enlace arbitrario).

Cierre

Cuanto más nos adentramos en los temas sobre los punteros, estamos más cerca de la máquina. Gran parte del poder de C y C + + de ella derivados, y un montón de problemas también. La complejidad va en aumento y los riesgos también. Para muchos ahí está la diversión!

Enlaces

Comentarios

    blog alimentado por los comentarios Disqus