Prefacio: Explicación de tipos complejos
Para entender los punteros, aparecerán más o menos algunos tipos bastante complejos, así que primero presentaré cómo comprender completamente un tipo complejo. En realidad, entender un tipo complejo es muy sencillo: en un tipo aparecen muchos operadores, y al igual que las expresiones normales, tienen precedencia; su precedencia es la misma que la precedencia de los operadores. Por lo tanto, he resumido el principio: comenzando desde el nombre de la variable, se asocia según la precedencia de los operadores, y se analiza paso a paso.
A continuación, empecemos analizando poco a poco con los tipos simples:
- int p;-- Esto es una variable entera normal.
- int *p;-- Primero, comenzando desde p, se realiza la asociación*dicha asociación indica que p es un puntero; luego se asocia con int, lo que indica que el tipo del contenido al que apunta el puntero es int. Por lo tanto, p es un puntero que apunta a datos enteros.
- int p[3]-- Primero, comenzando desde p, se realiza la asociación[]dicha asociación indica que p es un array; luego se asocia con int, lo que indica que los elementos del array son de tipo entero. Por lo tanto, p es un array compuesto por datos enteros.
- int *p[3];-- Primero, comenzando desde p, se asocia primero con [], porque su precedencia es mayor que*la de *, por lo que p es un array; luego se asocia con *, lo que indica que los elementos del array son de tipo puntero; luego se asocia con int, lo que indica que el tipo del contenido al que apunta el puntero es entero. Por lo tanto, p es un array compuesto por punteros que apuntan a datos enteros.
- int (*p)[3];-- Primero, comenzando desde p, se asocia con *, lo que indica que p es un puntero; luego se asocia con [] (el paso con "()" se puede ignorar, solo es para cambiar la precedencia), lo que indica que el contenido al que apunta el puntero es un array; luego se asocia con int, lo que indica que los elementos del array son de tipo entero. Por lo tanto, p es un puntero que apunta a un array compuesto por datos enteros.
- int **p;-- Primero, comenzando desde p, se asocia con *, lo que indica que p es un puntero; luego se asocia con * nuevamente, lo que indica que el elemento al que apunta el puntero es un puntero; luego se asocia con int, lo que indica que el elemento al que apunta ese puntero es un dato entero. Dado que los punteros de segundo nivel y de niveles superiores rara vez se usan en tipos complejos, en los tipos más complejos posteriores no consideraremos punteros de múltiples niveles; como máximo consideraremos punteros de un solo nivel.
- int p(int);-- Partiendo de p, primero se asocia con (), lo que indica que p es una función; luego se analiza dentro de (), lo que indica que la función tiene un parámetro de variable entera; luego se asocia con el int exterior, lo que indica que el valor de retorno de la función es un dato entero.
- int (*p)(int);-- Comenzando desde p, primero se asocia con el puntero, lo que indica que p es un puntero; luego se asocia con (), lo que indica que el puntero apunta a una función; luego se asocia con el int dentro de (), lo que indica que la función tiene un parámetro de tipo int; luego se asocia con el int más externo, lo que indica que el tipo de retorno de la función es entero. Por lo tanto, p es un puntero que apunta a una función con un parámetro entero y cuyo tipo de retorno es entero.
- int *(*p(int))[3];-- Se puede omitir por ahora, sin mirar este tipo, es demasiado complejo. Comenzando desde p, primero se asocia con (), lo que indica que p es una función; luego, entrando dentro de (), se asocia con int, lo que indica que la función tiene un parámetro de variable entera; luego se asocia con el * exterior, lo que indica que la función retorna un puntero; luego, en la capa más externa, primero se asocia con [], lo que indica que el puntero retornado apunta a un array; luego se asocia con *, lo que indica que los elementos del array son punteros; luego se asocia con int, lo que indica que el contenido al que apuntan los punteros son datos enteros. Por lo tanto, p es una función que tiene un parámetro de dato entero y retorna un puntero a un array compuesto por variables puntero a enteros.
Con esto ya es casi todo; nuestra tarea llega hasta aquí. Una vez entendidos estos tipos, los demás tipos serán pan comido para nosotros. Sin embargo, generalmente no usamos tipos demasiado complejos, porque eso reduciría en gran medida la legibilidad del programa. Por favor, úsenlos con precaución; los tipos mencionados arriba ya son suficientes para nosotros.
I. Punteros en detalle
Un puntero es una variable especial; el valor numérico almacenado en él se interpreta como una dirección en la memoria. Para entender bien un puntero, es necesario aclarar cuatro aspectos del puntero: el tipo del puntero, el tipo al que apunta el puntero, el valor del puntero (también llamado área de memoria o dirección a la que apunta el puntero), y el área de memoria que ocupa el propio puntero. Vamos a explicarlos por separado.
Primero declaremos algunos punteros como ejemplo:
Ejemplo:
1. El tipo del puntero
Desde el punto de vista sintáctico, sólo tienes que quitar el nombre del puntero en la declaración de puntero; la parte restante es el tipo de este puntero. Este es el tipo que posee el propio puntero. Veamos los tipos de los punteros en el Ejemplo 1:
- 1, int *ptr;: el tipo del puntero esint*
- 2, char *ptr;: el tipo del puntero eschar*
- 3, int **ptr;: el tipo del puntero esint**
- 4, int (*ptr)[3];: el tipo del puntero esint(*)[3]
- 5, int *(*ptr)[4]; : el tipo del puntero esint*(*)[4]
¿Qué tal? ¿No es muy sencillo el método para averiguar el tipo de un puntero?
2. El tipo al que apunta el puntero
Cuando accedes a través del puntero al área de memoria a la que apunta el puntero, el tipo al que apunta el puntero determina cómo tratará el compilador el contenido de esa área de memoria.
Desde el punto de vista sintáctico, sólo tienes que eliminar el nombre del puntero y el declarador de puntero * a la izquierda del nombre en la declaración de puntero; lo que quede es el tipo al que apunta el puntero. Por ejemplo:
- 1, int*ptr;: el tipo al que apunta el puntero esint
- 2, char*ptr;: el tipo al que apunta el puntero eschar
- 3, int**ptr;: el tipo al que apunta el puntero esint*
- 4, int(*ptr)[3];: el tipo al que apunta el puntero esint()[3]
- 5, int*(*ptr)[4]; : el tipo al que apunta el puntero esint*()[4]
En las operaciones aritméticas de punteros, el tipo al que apunta el puntero juega un papel muy importante.
El tipo del puntero (es decir, el tipo del propio puntero) y el tipo al que apunta el puntero son dos conceptos. A medida que te familiarizas más con C, descubrirás que dividir el concepto de "tipo" relacionado con los punteros en dos conceptos, "el tipo del puntero" y "el tipo al que apunta el puntero", es uno de los puntos clave para dominar los punteros. He leído muchos libros y he encontrado que en algunos libros mal escritos mezclan estos dos conceptos de los punteros, por lo que la lectura resulta contradictoria y cuanto más se lee, más confuso se vuelve.
3. El valor del puntero----o también llamado área de memoria o dirección a la que apunta el puntero
El valor del puntero es el valor numérico almacenado en el propio puntero. Este valor será tratado por el compilador como una dirección, no como un valor numérico ordinario. En un programa de 32 bits, los valores de todos los tipos de punteros son enteros de 32 bits, porque en un programa de 32 bits todas las direcciones de memoria tienen 32 bits de longitud. El área de memoria a la que apunta el puntero es un área de memoria que comienza en la dirección de memoria representada por el valor del puntero y tiene una longitud de sizeof(tipo al que apunta el puntero). En adelante, cuando decimos que el valor de un puntero es XX, equivale a decir que el puntero apunta a un área de memoria cuya dirección inicial es XX; cuando decimos que un puntero apunta a un cierto bloque de memoria, equivale a decir que el valor de ese puntero es la dirección inicial de ese bloque de memoria. El área de memoria a la que apunta el puntero y el tipo al que apunta el puntero son dos conceptos completamente diferentes. En el Ejemplo 1, el tipo al que apunta el puntero ya existe, pero como el puntero no ha sido inicializado, el área de memoria a la que apunta no existe, o no tiene significado.
En el futuro, cada vez que encuentres un puntero, debes preguntarte: ¿cuál es el tipo de este puntero? ¿Cuál es el tipo al que apunta el puntero? ¿Hacia dónde apunta este puntero? (Presta especial atención)
4. El área de memoria que ocupa el propio puntero
¿Cuánta memoria ocupa el propio puntero? Sólo tienes que usar la función sizeof(tipo del puntero) para medirlo. En una plataforma de 32 bits, el puntero en sí ocupa 4 bytes. El concepto de la memoria que ocupa el propio puntero es muy útil para determinar si una expresión de puntero (se explicará más adelante) es un lvalue.
II. Operaciones aritméticas con punteros
Un puntero puede sumarse o restarse con un entero. El significado de esta operación con punteros no es el mismo que el de las operaciones ordinarias de suma y resta numéricas; se realiza en unidades de elemento. Por ejemplo:
En el ejemplo anterior, el tipo del puntero ptr es int*, el tipo al que apunta es int, y se inicializa para apuntar a la variable entera a. En la tercera sentencia que sigue, el puntero ptr se incrementa en 1; el compilador lo maneja así: suma sizeof(int) al valor del puntero ptr; en un programa de 32 bits, se suma 4, porque en un programa de 32 bits, int ocupa 4 bytes. Dado que las direcciones se miden en bytes, la dirección a la que apunta ptr aumenta 4 bytes desde la dirección de la variable original a hacia la dirección más alta. Como la longitud del tipo char es un byte, originalmente ptr apuntaba a los cuatro bytes que comienzan en el elemento número 0 del array a; ahora apunta a los cuatro bytes que comienzan en el elemento número 4 del array a. Podemos usar un puntero y un bucle para recorrer un array; veamos el ejemplo:
Este ejemplo suma 1 al valor de cada elemento del array de enteros. Como en cada bucle el puntero ptr se incrementa en 1 elemento, cada bucle puede acceder al siguiente elemento del array.
Veamos otro ejemplo:
En este ejemplo, a ptr se le suma 5. El compilador lo procesa así: suma al valor del puntero ptr 5 por sizeof(int); en un programa de 32 bits, suma 5 por 4 = 20. Como la unidad de dirección es el byte, la dirección a la que apunta el ptr actual se ha movido 20 bytes hacia la dirección alta en comparación con la dirección a la que apuntaba ptr antes de sumar 5.
En este ejemplo, antes de sumar 5, ptr apuntaba a los cuatro bytes que comienzan en la celda 0 del array a; después de sumar 5, ptr ya apunta fuera del rango válido del array a. Aunque esta situación pueda causar problemas en la aplicación, sintácticamente es válida. Esto también refleja la flexibilidad de los punteros. Si en el ejemplo anterior a ptr se le restara 5, el proceso sería similar, solo que el valor de ptr se restaría 5 por sizeof(int), y la nueva dirección a la que apunta ptr se movería 20 bytes hacia la dirección baja con respecto a la dirección a la que apuntaba el ptr original.
A continuación, permítanme dar otro ejemplo: (un malentendido)
Malentendido uno: la salida es Y y o
Malinterpretación: ptr es un puntero doble a char. Cuando se ejecuta ptr++, el puntero se incrementa en un sizeof(char), por lo que la salida es la anterior. Esto puede ser el resultado de una minoría.Malentendido dos:La salida es Y y a. Malinterpretación: ptr apunta a un tipo char*. Cuando se ejecuta ptr++, el puntero se incrementa en un sizeof(char*) (algunos podrían pensar que este valor es 1, y entonces obtendrían la respuesta del malentendido uno; este valor debería ser 4, consulte el contenido anterior), es decir, &p+4; entonces, al realizar una operación de indirección, ¿no apuntaría al quinto elemento del array? ¿No sería la salida el quinto elemento del array? La respuesta es negativa.
Solución correcta:El tipo de ptr es char **, el tipo al que apunta es un tipo char*, la dirección a la que apunta es la dirección de p (&p). Cuando se ejecuta ptr++, el puntero se incrementa en un sizeof(char*), es decir, &p+4; entonces, ¿a dónde apunta *(&p+4)? Pregúntale a Dios, tal vez él te diga dónde. Por lo tanto, la salida final será un valor aleatorio, o quizás una operación ilegal.
. Pero si se considera array como un puntero, apunta a la celda 0 del array, su tipo es int* y el tipo al que apunta es el tipo de las celdas del array, es decir, int. Por lo tanto, no es de extrañar que *array sea igual a 0. Del mismo modo, array+3 es un puntero que apunta a la celda 3 del array, por lo que
Cuando un puntero ptrold se suma (o resta) un entero n, el resultado es un nuevo puntero ptrnew. El tipo de ptrnew es el mismo que el de ptrold, y el tipo al que apunta ptrnew también es el mismo que el tipo al que apunta ptrold. El valor de ptrnew será el valor de ptrold aumentado (o disminuido) en n por sizeof(tipo al que apunta ptrold) bytes. Es decir, la región de memoria a la que apunta ptrnew se habrá movido n por sizeof(tipo al que apunta ptrold) bytes hacia la dirección alta (o baja) en comparación con la región de memoria a la que apunta ptrold. Suma y resta entre punteros: dos punteros no pueden realizar una operación de suma; esto es una operación ilegal, porque después de la suma, el resultado apunta a un lugar desconocido y no tiene ningún significado. Dos punteros pueden realizar una operación de resta, pero deben ser del mismo tipo; generalmente se usa en arrays, no diré más.
III. Los operadores & y *
Aquí&es el operador de dirección,*es el operador de indirección.
&aEl resultado de la operación de & es un puntero. El tipo del puntero es el tipo de a más un *. El tipo al que apunta el puntero es el tipo de a. En cuanto a la dirección a la que apunta el puntero, es la dirección de a.
*pEl resultado de la operación de * es variado. En resumen, el resultado de *p es aquello a lo que apunta p. Esta cosa tiene estas características: su tipo es el tipo al que apunta p, y la dirección que ocupa es la dirección a la que apunta p.
Ejemplo:
IV. Expresiones de puntero
Si el resultado de una expresión es un puntero, entonces esa expresión se llama expresión de puntero.
A continuación se muestran algunos ejemplos de expresiones de puntero:
Ejemplo:
Dado que el resultado de una expresión de puntero es un puntero, la expresión de puntero también tiene los cuatro elementos de un puntero: el tipo del puntero, el tipo al que apunta el puntero, la región de memoria a la que apunta el puntero y la memoria que ocupa el propio puntero.
Bien, cuando el puntero resultante de una expresión de puntero ya tiene claramente la memoria que ocupa el propio puntero, entonces esta expresión de puntero es un lvalue; de lo contrario, no lo es. En el ejemplo siete, &a no es un lvalue, porque todavía no ocupa memoria definida. *ptr es un lvalue, porque el puntero *ptr ya ha ocupado memoria; de hecho, *ptr es el puntero pa. Dado que pa ya tiene su propia posición en la memoria, por supuesto *ptr también tiene su propia posición.
V. Relación entre arrays y punteros
El nombre de un array en realidad puede considerarse como un puntero. Vea el siguiente ejemplo:
En el ejemplo anterior, en general, el nombre del array array representa al propio array, con tipo int*(array+3)es igual a 3. Los demás casos se deducen de manera similar.
Ejemplo:
En el ejemplo anterior, str es un array de tres celdas; cada celda del array es un puntero, y estos punteros apuntan cada uno a una cadena. Si se considera el nombre del array de punteros str como un puntero, apunta a la celda 0 del array, su tipo es char ** y el tipo al que apunta es char *.
*str también es un puntero, su tipo es char *, el tipo al que apunta es char, y la dirección a la que apunta es la dirección del primer carácter de la cadena "Hello,thisisasample!", es decir, la dirección de 'H'. Nota: una cadena equivale a un array; se almacena en memoria en forma de array, solo que la cadena es una constante de array, su contenido no se puede modificar, y solo puede ser un rvalue. Si se considera como un puntero, es tanto un puntero constante como una constante de puntero.
str+1 también es un puntero, apunta a la celda 1 del array, su tipo es char** y el tipo al que apunta es char*.
*(str+1) también es un puntero, su tipo es char*, el tipo al que apunta es char, y apunta al primer carácter 'H' de "Hi,goodmorning.".
A continuación se resumen las cuestiones sobre el nombre de un array (cuando lo que se almacena en el array también son arrays):
Se declara un arrayTYPE array[n], entonces el nombre del array array tiene dos significados:
-
Primero, representa a todo el array, su tipo esTYPE[n];
-
Segundo, es un puntero constante; el tipo de este puntero esTYPE*, el tipo al que apunta este puntero esTYPE, es decir, el tipo de las celdas del array. La región de memoria a la que apunta este puntero es la celda 0 del array. Este puntero ocupa por sí mismo una región de memoria separada; nótese que es diferente de la región de memoria ocupada por la celda 0 del array. El valor de este puntero no se puede modificar, es decir, expresiones como array++ son incorrectas. En diferentes expresiones, el nombre del array array puede desempeñar diferentes roles. En la expresión sizeof(array), el nombre del array array representa al propio array, por lo que en este caso la función sizeof mide el tamaño de todo el array.
En la expresión *array, array desempeña el papel de un puntero, por lo que el resultado de esta expresión es el valor de la celda 0 del array.sizeof(*array)Lo que se mide es el tamaño de las celdas del array.
En la expresión array+n (donde n=0,1,2,...), array actúa como un puntero, por lo que el resultado de array+n es un puntero, cuyo tipo esTYPE *, el tipo al que apunta es TYPE, y apunta al elemento número n del array. Por lo tanto, sizeof(array+n) mide el tamaño del tipo puntero. En un programa de 32 bits el resultado es 4.
Ejemplo:
En el ejemplo anterior, ptr es un puntero, su tipo esint(*)[10], el tipo al que apunta es int
En esta sección se mencionó la función sizeof(). Entonces pregunto: ¿sizeof(nombre_de_puntero) mide en realidad el tamaño del tipo del propio puntero o el tamaño del tipo al que apunta el puntero?
La respuesta es lo primero. Por ejemplo:
int(*ptr)[10];
Entonces, en un programa de 32 bits, se tiene:
sizeof(int(*)[10])==4 sizeof(int[10])==40 sizeof(ptr)==4
En realidad, sizeof(objeto) mide siempre el tamaño del tipo del propio objeto, no el tamaño de ningún otro tipo.
VI. Relación entre punteros y tipos de estructura
Se puede declarar un puntero a un objeto de tipo estructura. Ejemplo:
¿Cómo se accede a las tres variables miembro de ss mediante el puntero ptr?
Respuesta:
ptr->a; //指向运算符,或者可以这们(*ptr).a,建议使用前者 ptr->b; ptr->c;
Y entonces, ¿cómo se accede a las tres variables miembro de ss mediante el puntero pstr?
Respuesta:
*pstr; //访问了ss 的成员a. *(pstr+1); //访问了ss 的成员b. *(pstr+2) //访问了ss 的成员c.
Aunque he depurado el código anterior en mi MSVC++6.0, hay que saber que usar pstr de esta manera para acceder a los miembros de una estructura no es estándar. Para explicar por qué no es estándar, veamos cómo se accede a los distintos elementos de un array mediante un puntero: (sustituyendo la estructura por un array)
En cuanto al formato, se parece al formato del método no estándar de acceder a los miembros de una estructura mediante un puntero.
Todos los compiladores de C/C++, al disponer los elementos de un array, colocan siempre los distintos elementos del array en un área de almacenamiento contigua, sin huecos entre elemento y elemento. Pero al almacenar los miembros de un objeto de estructura, en algún entorno de compilación puede ser necesario alineación a palabra, a doble palabra o cualquier otra alineación, y es necesario añadir varios “bytes de relleno” entre dos miembros adyacentes, lo que hace que entre los miembros pueda haber varios bytes de hueco.
Por lo tanto, en el ejemplo 12, incluso si *pstr accede a la primera variable miembro a de la estructura ss, no se puede garantizar que *(pstr+1) vaya a acceder necesariamente al miembro de estructura b. Porque entre los miembros a y b puede haber varios bytes de relleno, y quizás *(pstr+1) acceda justamente a esos bytes de relleno. Esto también demuestra la flexibilidad de los punteros. Si tu propósito es precisamente ver si hay bytes de relleno entre los miembros de una estructura, bueno, ese es un buen método.
Sin embargo, el método correcto para que un puntero acceda a los miembros de una estructura debería ser como el método que usa el puntero ptr en el ejemplo 12.
VII. Relación entre punteros y funciones
Se puede declarar un puntero como un puntero a una función.
int fun1(char *,int);
int (*pfun1)(char *,int);
pfun1=fun1;
int a=(*pfun1)("abcdefg",7); //通过函数指针调用函数。
Se puede usar un puntero como parámetro formal de una función. En la sentencia de llamada a la función, se puede usar una expresión de puntero como argumento real.
En este ejemplo, la función fun calcula la suma de los valores ASCII de cada carácter de una cadena. Como se dijo antes, el nombre de un array también es un puntero. En la llamada a la función, cuando se pasa str como argumento real al parámetro formal s, en realidad se pasa el valor de str a s; la dirección a la que apunta s coincide con la dirección a la que apunta str, pero str y s ocupan cada uno su propio espacio de almacenamiento. Realizar la operación de incremento en 1 sobre s dentro del cuerpo de la función no significa que al mismo tiempo se haya incrementado str en 1.
VIII. Conversión de tipos de puntero
Cuando inicializamos un puntero o asignamos un valor a un puntero, el lado izquierdo del signo de asignación es un puntero y el lado derecho es una expresión de puntero. En los ejemplos que hemos visto antes, en la gran mayoría de los casos, el tipo del puntero es el mismo que el tipo de la expresión de puntero, y el tipo al que apunta el puntero es el mismo que el tipo al que apunta la expresión de puntero.
En el ejemplo anterior, si queremos que el puntero p apunte al número real f, ¿qué deberíamos hacer?
¿Usamos la siguiente sentencia?
p=&f;
No. Porque el tipo del puntero p es int *, y el tipo al que apunta es int. El resultado de la expresión &f es un puntero; el tipo del puntero es float *, y el tipo al que apunta es float.
Los dos no coinciden, por lo que el método de asignación directa no funciona. Al menos en mi MSVC++6.0, la sentencia de asignación para punteros exige que los tipos a ambos lados del signo de asignación sean iguales, y que el tipo al que apuntan también sea igual. No lo he probado en otros compiladores; ustedes pueden probarlo. Para lograr nuestro objetivo, es necesario realizar una “conversión forzada de tipos”:
p=(int*)&f;
Si tenemos un puntero p y necesitamos cambiar su tipo y el tipo al que apunta aTYEP *TYPE, entonces el formato sintáctico es:(TYPE *)p
El resultado de esta conversión forzada de tipos es un nuevo puntero; el tipo de este nuevo puntero esTYPE *, el tipo al que apunta esTYPEy la dirección a la que apunta es la dirección a la que apuntaba el puntero original. Sin embargo, todos los atributos del puntero original p no han sido modificados. (Recuérdalo bien).
Si una función usa punteros como parámetros formales, entonces durante el enlace entre argumentos reales y parámetros formales en la sentencia de llamada a la función, se debe garantizar que los tipos coincidan; de lo contrario, se necesita una conversión forzada:
Observa que este es un programa de 32 bits, por lo que el tipo int ocupa cuatro bytes y el tipo char ocupa un byte. La función fun sirve para invertir el orden de los cuatro bytes de un entero. ¿Te has dado cuenta? En la sentencia de llamada a la función, el resultado del argumento real &a es un puntero, su tipo es int * y el tipo al que apunta es int. El tipo de este puntero del parámetro formal es char *, y el tipo al que apunta es char. Así, durante el enlace entre argumento real y parámetro formal, debemos realizar una conversión del tipo int * al tipo char *.
Con este ejemplo, podemos hacerlo así:
Imagina el proceso de conversión que realiza el compilador: primero construye un puntero temporal char *temp, luego ejecuta temp=(char *)&a, y finalmente pasa el valor de temp a s. Así que el resultado final es: el tipo de s es char *, el tipo al que apunta es char, y la dirección a la que apunta es la dirección inicial de a.
Ya sabemos que el valor de un puntero es la dirección a la que apunta; en un programa de 32 bits, el valor de un puntero es en realidad un entero de 32 bits.
Entonces, ¿se puede asignar directamente un entero a un puntero como si fuera el valor del puntero? Por ejemplo, en la siguiente sentencia:
Estrictamente hablando, el (TYPE *) de aquí no es igual al (TYPE *) de la conversión de tipos de puntero. Aquí, (TYPE*) significa tratar el valor del entero sin signo a como una dirección. Arriba se enfatizó que el valor de a debe representar una dirección válida; de lo contrario, cuando uses ptr, se producirá un error de operación ilegal. Piensa si se puede hacer lo contrario: extraer la dirección a la que apunta el puntero, es decir, el valor del puntero, como un entero. Totalmente posible. El siguiente ejemplo demuestra cómo extraer el valor de un puntero como un entero y luego asignar ese entero como una dirección a un puntero:
Ahora ya sabemos que se puede extraer el valor de un puntero como un entero, y también se puede asignar un valor entero a un puntero como si fuera una dirección.
IX. Problemas de seguridad de los punteros
Observa el siguiente ejemplo:
El puntero ptr es un puntero de tipo int *, y el tipo al que apunta es int. La dirección a la que apunta es la dirección inicial de s. En un programa de 32 bits, s ocupa un byte y el tipo int ocupa cuatro bytes. La última sentencia no solo cambia el byte que ocupa s, sino que también cambia los tres bytes adyacentes a s en el sentido de las direcciones más altas. ¿Para qué sirven estos tres bytes? Solo lo sabe el compilador, y es poco probable que la persona que escribe el programa lo sepa. Quizás en estos tres bytes se almacenan datos muy importantes, quizás son exactamente una instrucción del programa, y debido a tu uso descuidado del puntero, ¡el valor de estos tres bytes ha sido cambiado! Esto puede causar un error catastrófico.
Veamos otro ejemplo:
Este ejemplo puede compilarse perfectamente y puede ejecutarse. Pero, ¿lo ves? Después de la operación de incremento en 1 sobre el puntero ptr en la tercera sentencia, ptr apunta a un área de almacenamiento adyacente a la variable entera a en el sentido de las direcciones más altas. ¿Qué hay en esta área de almacenamiento? No lo sabemos. Podría ser un dato muy importante, incluso podría ser una instrucción.
¡Y la cuarta sentencia incluso escribe un dato en esta área de almacenamiento! Esto es un error grave. Por lo tanto, al usar punteros, el programador debe tener muy claro: a dónde apunta exactamente mi puntero. Al usar punteros para acceder a arrays, también hay que tener cuidado de no sobrepasar los límites inferior y superior del array; de lo contrario, se producirán errores similares.
En la conversión forzada de tipos de punteros:ptr1=(TYPE *)ptr2En ella, si sizeof(tipo de ptr2) es mayor que sizeof(tipo de ptr1), entonces es seguro usar el puntero ptr1 para acceder al área de almacenamiento a la que apunta ptr2. Si sizeof(tipo de ptr2) es menor que sizeof(tipo de ptr1), entonces no es seguro usar el puntero ptr1 para acceder al área de almacenamiento a la que apunta ptr2. En cuanto al porqué, el lector debería entenderlo pensando junto con el ejemplo 18.