Apuntadores y arreglos

18 de mayo 2009 · Escrito 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 vamos a explorar la íntima relación (ui!) entre apuntadores y arreglos (o matrices).

Arrays recordando

Un arreglo o matriz, o un acuerdo, 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 se organiza en un formato de tabla con filas y columnas. Cada elemento de la matriz tiene coordenadas únicas (fila y columna), por lo que un determinado elemento E (i, j) representa el único elemento en el "corredor de la i", "la columna j".

La sintaxis de la declaración de matriz es la siguiente:

  / / Una matrices bidimensionales o vectores
 10 ] ; // 10 elementos, do 0 ao 9 FPI int [10] / / 10 elementos, 0-9
 23 ] ; // 23 elementos, do 0 ao 22 FPC char [23] / / 23 elementos, desde 0 hasta 22

 / / Dos matrices bidimensionales
 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 a la siguiente sintaxis:

 10 ] ; int imat [ 3 ] [ 4 ] ; // Alterando o quarto elemento do vetor ivet. // Lembre-se que começa-se a contar a partir do elemento zero ivet [ 3 ] = 13 ; // Lendo o segundo elemento do vetor ivet int num = ivet [ 1 ] ; // Alterando o elemento da primeira linha, segunda coluna de imat imat [ 0 ] [ 1 ] = 42 ; // Lendo o elemento na terceira linha, quarta coluna de imat int foo = imat [ 2 ] [ 3 ] ; FPI int [10]; IMAT int [3] [4] / / Cambiar el cuarto elemento del vector FPI / / Recuerda que una empieza a contar desde cero elemento FPI [3] = 13 / /. Lectura del segundo elemento de la matriz int a = FP FP [1] / / Cambiar el elemento de la primera fila, segunda columna IMAT IMAT [0] [1] = 42 / / Lectura del elemento en la tercera fila, cuarta columna IMAT IMAT int foo = [2] [3]; 

En este post no vamos a discutir las matrices de fondo, sólo se investigará la relación entre arreglos y apuntadores de una manera bastante intuitiva.

Tamaños de las matrices

Dado que una matriz es una abstracción que puede tener varios valores del mismo tipo, lo que el tamaño? ¿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 ] ; FPI int [10];
 13 ] ; FPC char [13];
 20 ] ; ETVD doble [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 (FPI) =" <<sizeof (FPI) <<endl;
 "sizeof(cvet) = " << sizeof ( cvet ) << endl ; cout <<"sizeof (FPC) =" <<sizeof (FPC) <<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 por el tamaño del tipo de elemento (filas x columnas x sizeof (tipo)). Ahora bien, si cada elemento es independiente, se supone que cada uno ocupe un lugar separado en la memoria, de lo contrario sobrescribir el otro elemento. Así es que cada elemento tiene su propia dirección de la memoria?

Las matrices y las direcciones de memoria

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

  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' } ; FPC 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 ("index \ t valor \ elemento de dirección t \ 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, FPC [i], y FPC [i]);
 }

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

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

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

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

En C + +, un conjunto común es un bloque contiguo de memoria cuyo nombre puede ser interpretado (emitidos) como un puntero que apunta a su primer elemento. Además, es un puntero válido a punto de una serie, siempre y cuando el puntero hasta el destino es el mismo tipo que el tipo de los elementos de la matriz. Durante atrubuição de una matriz a un puntero, el compilador realiza una conversión de tipos implícita. El puntero del objetivo debe ser interpretado como un puntero a la zona de memoria ocupada por la matriz.

Una de las consecuencias no tan obvia es la que está implícita en el reparto, se pierde la información de que la zona era un arreglo de memoria. Así que la información se pierde en el tamaño de la matriz. Desde el punto de vista del puntero, al que hace referencia el principio de un bloque arbitrario de la memoria, de un tamaño demasiado arbitraria. Salida e ir a por una matriz a un puntero que representa pasar de un enfoque más restrictivo y más abstracta de alto nivel de abstracción para un nivel menos restrictivo y más bajos.

Por otro lado intenta asignar un puntero a un array genera un error de compilación de tipos incompatibles. Un arreglo es un bloque de memoria de n (bytes) como un puntero sólo ha dado, una dirección. El compilador no tiene manera de saber de antemano si un puntero apunta a un área de bytes 1, 2 y 200.

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

 " \n Antes da atribuição \n " ) ; printf ("\ n Antes de la asignación de \ n");
 "cvet = %p \n " , cvet ) ; printf ("FPC =% p \ n", FPC);
 "pc = %p \n " , pc ) ; printf ("pc =% p \ n", pc);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (FPC) =% lu \ n", sizeof (FPC));
 "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (cp) =% lu \ n", sizeof (cp));

 pc = FPC;

 " \n Depois da atribuição \n " ) ; printf ("\ n Después de la asignación de \ n");
 "cvet = %p \n " , cvet ) ; printf ("FPC =% p \ n", FPC);
 "pc = %p \n " , pc ) ; printf ("pc =% p \ n", pc);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (FPC) =% lu \ n", sizeof (FPC));
 "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (cp) =% lu \ n", sizeof (cp)); 

Tenga en cuenta que antes de la asignación (línea 25), el puntero del PC. es cero, por lo que fue inicializado. Ya que los tamaños indican que la matriz tiene sólo 300 bytes, y el puntero del 8 (mi máquina es un amd 64). Después de asignar ambos le transmiten el "punto" a la misma área de memoria, pero los tamaños no cambian. Hubo una conversión implícita de char [300] a char *, y en este juego de la pc puntero no puede saber el tamaño del área de memoria a la que apunta. Sin embargo, la matriz sigue FPC saber exactamente lo que es, sin ningún tipo de crisis existencial.

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

¿Por qué, si yo sé que los datos de una matriz están dispuestos uno junto al otro, puedo usar un puntero que va a pasar a la siguiente dirección y acceder al siguiente elemento. Se llama aritmética de punteros.

  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' } ; FPC char [max] = {'B', 'L', 'A', 'B', 'O', 'S'};

 pc = cvet ; char * pc = FPC;

 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 } ; FPI int [max] = {1, 2, 3, 4, 5, 6};
 pi = ivet ; int * pi = FPI;

 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 FPC matriz, y por lo tanto a su primer elemento, "B" del personaje. En la línea 21 el contenido de PC, que es la dirección donde se guardaba el 'B' del personaje, es decir, se incrementa luego de una referencia. En la primera pasada el valor de i es igual a cero, por lo que es el valor de eliminación de referencias a 'B'. En los pasos siguientes, la siguiente dirección se está de-hace referencia a otros personajes almacenados en la matriz original. Es más o menos lo que el compilador hace internamente cuando se utiliza la sintaxis de la FPC [i]. La abstracción de la matriz que da una manera fácil de tratar con las zonas contiguas de la memoria que * (pc + i).

Pero si la abstracción matriz es más fácil 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 similar, la sintaxis de la matriz, el acceso a los datos dados se puede resumir en los comandos de muy áspero:

  1. Tome la dirección base de la matriz;
  2. Añadir a la dirección del valor del índice;
  3. De-hacen referencia a esta nueva dirección;

Ya la aritmética de punteros es la siguiente:

  1. De-hacen referencia a esta dirección;

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

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

Una observación importante es que entre las líneas 31 y 38 se repite con toda la experiencia. Tenga en cuenta que a medida que los enteros son de 4 bytes, los incrementos se realizan automáticamente en 4 de 4 bytes, no 1 a 1, es decir, el incremento se calcula automáticamente el valor sizeof (tipo). Incrementar un puntero medios para acceder a la siguiente área de memoria similar a los datos reales, no sólo a la siguiente dirección. A medida que el tamaño de un char es un byte, cuando incremetamos un puntero a char, mover sólo un byte. Si incrementa 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 "lineal", como se muestra en las líneas 40 a 52. Esto es útil, en su caso, para hacer un mejor uso de la caché del procesador, por ejemplo.

void * pansexual de punteros

Anteriormente dije que sólo era posible asignar una matriz a un puntero que se fue a la misma como la matriz de datos. Me mintió abiertamente! La razón es que para alguien que abandonó el cargo antes de este tema, es más seguro que creer que no puede :) !

Hay dos excepciones a la regla. El primero es cuando hay una conversión de tipo explícito y el puntero del destino "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 es un puntero nulo que no exige nada del tipo de datos que están en el área de memoria a la que apunta. Es un puntero a un área general de la memoria, algo muy bajo nivel.

Para utilizar una base de datos apuntado por un puntero nulo, antes de hacer referencia a ella-, usted debe hacer una conversión explícita a un tipo válido, ya que es un int *, int, en base a un char * es de una referencia a char, adivinar lo que es una referencia a 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' } ; FPC char [max] = {'B', 'L', 'A', 'B', 'O', 'S'};

 pv = cvet ; void * pv = FPC;

 / / Error de compilación.
 / / * Pv
 / / El valle de 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 a void se utiliza cuando se necesita para apuntar a un área general de la memoria sin necesidad de control / conocimiento del tipo de datos que contiene esta área, o funciones que no pueden hacer suposiciones sobre los tipos de sus parámetros, tales como el API lib pthreads (enlace arbitrario).

Final

Cuanto más nos adentramos en los temas de los punteros, estamos más cerca de la máquina. Gran parte de la potencia de C y C + + viene de allí, y mucha de la culpa también. La complejidad es mayor y los riesgos. Para muchos ahí está la diversión!

Enlaces

Comentarios

  • Leandro Melo http://0xc0de.wordpress.com

    Así didáctico. Me gustó el "manos de pansexual". :)

  • Francisco-bruno-luisa

    rrrrr

Sitio en Disqus