[C con Clase] Punteros

Steven Davidson steven en conclase.net
Jue Feb 1 18:39:52 CET 2007


Hola David,

El pasado 2007-02-01 07:49:28, David escribió:

D> Hola a todos, tengo un pequeño problema con los punteros, veran yo uso MS Visual C++ 6.0 y al ejecutar el siguiente programa:
D> #include <iostream.h>
D> void main () {
D>     int a = 10, b = 100, c = 30, d = 1, e = 54;
D>     int m[10] = {10,20,30,41,50,60,70,80,90,100};
D>     int *p = &m[3], *q = &m[6];
D>     
D>     cout << "Son iguales? " << p << ", " << &m[3] << ", " << *p << endl;
D>     cout << "--------------------------------------------------------\n\n" << endl;
D>     cout << m[3] << " : " << &m[3] << " : " << p << " : " << *p << " : " << ++*p << " : " << *p++ << " : " << ++*p++ << endl;
D> Me da el siguiente resultado:
D> Son iguales? 0x0012FF50, 0x0012FF50, 41
D> --------------------------------------------------------
D> 42 : 0x0012FF50 : 0x0012FF58 : 61 : 61 : 50 : 42
D> Como pueden comprobar, cada vez que quiero imprimir el valor del puntero "*p" me suma 1 al valor al que apunta. Si no me equivoco, cuando mando a imprimir en pantalla "*p", debería salirme 40 y no 41. Alguien sabe que pasa, tal vez sea alguna directiva del compilador, pero hasta ahora no he dado con el fallo, si es que lo hay.

Vamos por partes. Te sale 41 porque escribiste 41 en la inicialización del array 'm'. En otro mensaje corregiste este valor inicial a 40, por lo que debería salirte 40. En cuanto a la diferencia de las direcciones de memoria, al igual que los valores impresos en la segunda línea, todo esto tiene que ver con un tema poco hablado y tratado acerca de los operadores de incremento y decremento junto con asignaciones.

Antes de explicar este tema, quiero hablar de otro para aclarar la evaluación de los operadores. Recuerda que los operadores y por tanto sus operaciones son evaluados en cierto orden según la tabla de precedencia y asociatividad. Puedes consultar el capítulo 14 del curso de C++. El enlace es: http://c.conclase.net/curso/index.php?cap=014  Aplicamos esta tabla a tu código. Las primeras operaciones evaluadas son 'm[3]'. Luego llegamos a los operadores unitarios de incremento (++), dirección de (&), y acceso o puntero a (*), que son evaluados de derecha a izquierda. La secuencia viene a ser la siguiente:

1. ++*p++  que viene a ser  (++(*(p++)))

Esto implica que a) incrementamos el puntero póstumamente, b) accedemos al valor apuntado, y c) incrementamos tal valor.

2. Luego evaluamos,

*p++  que es equivalente a  (*(p++))

Volvemos a incrementar el puntero póstumamente y luego accedemos a su valor.

3. Después tenemos que,

++*p  que equivale a  (++(*p))

Aquí, accedemos al valor apuntado para incrementarlo.

4. Seguidamente tenemos,

*p  que simplemente accede al valor apuntado.

5. Por último, obtenemos la dirección de memoria del valor-i(zquierdo) o en inglés "lvalue" de 'm[3]' con la operación '&m[3]'.

Al final, evaluamos los operadores binarios de << de izquierda a derecha. Esto es,

(...(((cout << m[3]) << " : ") << &m[3])...<<...)


Bien. Volvamos al tema "poco hablado" que mencioné al principio. Este tema tiene que ver con la asignación de variables que son incrementados o restados en la misma expresión. Por ejemplo,

int i=10;

++i = i++ + i++;

La pregunta es ¿cuál es el valor final de 'i' después de evaluar la expresión? Pues me temo que no hay una respuesta definitiva. Esto depende del diseño del compilador.

Algunos compiladores darán como respuesta: 24, ya que incrementa 'i' debido a '++i' para luego resultar en 22. Luego, incrementa dos veces este resultado por las dos operaciones de incremento póstumo: 'i++'.

Otros compiladores pueden decidir que el resultado es: 23, porque se realiza un solo incremento póstumo.

Y algunos otros compiladores pueden evaluar la expresión como 22, porque ignoran el incremento de 'i++' ya que esta operación modificaba el valor original de 11 (después del incremento antecedente de '++i'). Sin embargo, como 'i' es reasignado, entonces el incremento póstumo no tiene "validez".

Dicho lo anterior, puede haber otro compilador que resuelva el problema dando la solución: 13. Su "razonamiento" tiene que ver con la implementación de 'i++'. Primero hacemos el pre-incremento '++i' obteniendo 11. Al evaluar 'i++', el compilador asigna el valor incrementado a 'i' después de la expresión: de 11 á 12 y de 12 á 13. Se generaría una secuencia de instrucciones como la siguiente:

int i=10;

++i;
// i++
__copia__ = i;
__nuevo1__ = i+1;
__nuevo2__ = i+1;   // otro i++
i = __copia__ + __copia__;
i = __nuevo2__;  // final de i++


Existen otros problemas al evaluar algo similar con otras combinaciones de asignaciones e incrementos y reducciones tanto antecedentes como póstumos. La conclusión es que depende del compilador. La solución es no escribir algo así, sino ser más explícito. Por ejemplo, podemos reescribir la expresión anterior como:

int i=10;

++i;
i = i + i;
i++;
i++;


Espero haber aclarado las dudas.

Steven


Más información sobre la lista de distribución Cconclase