- Aprender
- Informática
- Programación
- Programación en C
- Punteros y direcciones de memoria en C
Punteros y direcciones de memoria en C
&, lee y modifica valores mediante punteros y pasa variables a funciones por referencia. Recorre arrays y cadenas mediante aritmética de punteros, utiliza punteros a punteros para los argumentos de la línea de comandos y para funciones que desplazan un puntero de la función llamadora, y llama a funciones mediante punteros a funciones, incluidas las funciones de comparación para la función estándar qsort.PrincipianteUnidad 4 de 8 del curso Curso de Programación en C
Lo que aprenderás
- Visualiza cómo las variables de un programa en C ocupan bytes en memoria, almacena direcciones en variables de tipo puntero y utiliza esos punteros para leer y modificar las variables a las que apuntan.
- Visualizar cómo se distribuyen las variables en memoriaDescribe la memoria como bytes numerados, averigua el tamaño y la dirección de variables y elementos de arrays, y explica cuánto tiempo existe cada tipo de variable.
- Declarar punteros y obtener direcciones con &Declara variables de tipo puntero del tipo adecuado, almacena la dirección de una variable con el operador de obtención de dirección e imprime, redirige o restablece un puntero de forma segura.
- Leer y modificar valores mediante punterosUtiliza el operador de desreferenciación para leer y escribir el objeto al que apunta un puntero, sigue la ejecución del código con diagramas de memoria y evita desreferenciar punteros no válidos.
- Visualizar cómo se distribuyen las variables en memoria
- Escribe funciones que modifiquen las variables de la función llamadora mediante parámetros de tipo puntero, protege los datos de solo lectura con
consty devuelve punteros que sigan siendo válidos.- Pasar variables por referencia mediante parámetros de tipo punteroExplica por qué las funciones de C reciben copias de sus argumentos y utiliza parámetros de tipo puntero para modificar variables de la función llamadora, intercambiar valores, devolver varios resultados y leer la entrada con
scanf. - Proteger los datos con const y devolver punteros de forma seguraMarca los datos de solo lectura con
const, distingue un puntero a datos constantes de un puntero constante y devuelve punteros que sigan siendo válidos después de que termine la función.
- Pasar variables por referencia mediante parámetros de tipo puntero
- Desplaza punteros por arrays y cadenas, explica cómo se relacionan los nombres de arrays con los punteros y mantén cada puntero dentro del array al que pertenece.
- Realizar operaciones aritméticas con punterosSuma enteros a punteros, resta y compara punteros a elementos del mismo array y reconoce qué resultados son válidos.
- Explicar la relación entre arrays y punterosUtiliza la conversión del nombre de un array a puntero, la identidad entre
a[i]y*(a + i)y las diferencias reales entre un array y un puntero. - Recorrer arrays y cadenas con punterosEscribe bucles que recorran arrays y cadenas terminadas en nulo con punteros, utiliza correctamente expresiones habituales como
*p++y mantente dentro de los límites.
- Realizar operaciones aritméticas con punteros
- Sigue varios niveles de indirección, permite que las funciones actualicen un puntero de la función llamadora y utiliza arrays de punteros como los argumentos de la línea de comandos.
- Utilizar punteros a punterosDeclara y desreferencia un puntero a puntero, sigue su recorrido en un diagrama de memoria y utilízalo para permitir que una función modifique un puntero de la función llamadora.
- Utilizar arrays de punteros y argumentos de la línea de comandosConstruye arrays de punteros a cadenas, procesa
argcyargvcomo punteros y distingue un array de punteros de un array bidimensional.
- Utilizar punteros a punteros
- Almacena punteros a funciones y llama a las funciones mediante ellos, pasa funciones a otras funciones y ordena datos de cualquier tipo con la función estándar
qsorty una función de comparación.- Declarar punteros a funciones, asignarles valores y llamar a funciones mediante ellosEscribe e interpreta declaraciones de punteros a funciones, llama a funciones mediante ellos y construye tablas de funciones que eligen el comportamiento durante la ejecución del programa.
- Escribir funciones de devolución de llamada y ordenar con qsortPasa funciones como argumentos, utiliza punteros
voidpara manejar datos de cualquier tipo y escribe funciones de comparación correctas paraqsort.
- Declarar punteros a funciones, asignarles valores y llamar a funciones mediante ellos
Guía de estudio de Punteros y direcciones de memoria en C
1Memoria, direcciones y valores apuntados
Una variable tiene un valor, ocupa almacenamiento y tiene un tiempo de vida. Un puntero te permite acceder a esa variable mediante su dirección, por lo que leer código ahora implica seguir tanto la dirección contenida en un puntero como el valor que se encuentra allí. Comienza por la distribución de los bytes y después sigue los efectos de obtener una dirección, redirigir un puntero y escribir mediante él. Los diagramas de memoria pequeños hacen visibles estas operaciones antes de que aparezcan dentro de funciones y bucles.
1.1 Bytes, tamaños y tiempos de vida
La memoria puede representarse como una secuencia de bytes numerados. Cada número es una dirección. Un objeto como un int ocupa bytes consecutivos y su dirección identifica su primer byte. El nombre que utilizas en el código fuente permite al compilador encontrar ese objeto; el lenguaje no exige que se almacene una etiqueta con el nombre junto a su valor.
Medir el almacenamiento con sizeof
sizeof indica los bytes y produce un valor size_t. Utiliza %zu para imprimirlo:
int scores[4] = {12, 18, 15, 21};
printf("%zu %zu\n", sizeof scores, sizeof scores[0]);
Con valores int de 4 bytes, esto imprime 16 4. El primer resultado mide los cuatro elementos; el segundo mide uno. sizeof(char) siempre es 1. Una disposición habitual en equipos de escritorio de 64 bits utiliza valores int de 4 bytes, valores double de 8 bytes y punteros a objetos de 8 bytes. Otros sistemas pueden diferir. Incluso entre sistemas de 64 bits, long suele ocupar 8 bytes en Linux y macOS, pero 4 en Windows.
El tamaño del almacenamiento y la longitud del texto responden a preguntas distintas. Para char word[20] = "hi";, sizeof word es 20, mientras que strlen(word) es 2. El array sigue conteniendo 20 elementos aunque su cadena utilice solo dos letras y un terminador. Salvo en los arrays de longitud variable, estos tamaños de objeto se fijan en tiempo de compilación.
Seguir las direcciones de un array
Las direcciones hexadecimales utilizan los dígitos de 0 a 9 y luego de a a f. El prefijo 0x indica la base 16. Para un array ilustrativo que comienza en 0x1000, cuatro elementos de 4 bytes comienzan en:
| Elemento | Primer byte | Último byte |
|---|---|---|
scores[0] | 0x1000 | 0x1003 |
scores[1] | 0x1004 | 0x1007 |
scores[2] | 0x1008 | 0x100B |
scores[3] | 0x100C | 0x100F |
0x100C es 4108 en decimal, doce bytes después de 0x1000. Los elementos de un array no tienen espacios entre ellos. No se garantiza el orden ni la separación de las variables declaradas por separado; las direcciones reales impresas también pueden cambiar entre ejecuciones. Un compilador puede mantener un valor en un registro del procesador cuando no se necesita almacenamiento con dirección.
Alineación y orden de los bytes
La alineación restringe dónde pueden comenzar los objetos de un tipo. Una disposición habitual sitúa los valores int de 4 bytes en múltiplos de 4 y los valores double de 8 bytes en múltiplos de 8. El compilador proporciona una alineación adecuada para los objetos declarados.
El orden de los bytes es el orden de los bytes dentro de un valor de varios bytes. Para un unsigned int de 4 bytes que contiene 0x12345678, el almacenamiento con el byte menos significativo primero tiene los bytes 78 56 34 12 de las direcciones menores a las mayores; el almacenamiento con el byte más significativo primero tiene 12 34 56 78. Un unsigned char * puede examinar la representación de un objeto byte a byte. El orden de los bytes no cambia el orden de los índices del array.
Mantener el objeto activo
Una variable local ordinaria tiene duración automática del almacenamiento: su tiempo de vida dura mientras se ejecuta su bloque. Las implementaciones típicas mantienen esos objetos en el marco de pila de una llamada y reutilizan ese almacenamiento después. Las variables globales, las variables static y los literales de cadena duran todo el programa. Guardar una dirección no prolonga el tiempo de vida de su objeto.
Puntos clave
sizeofmide bytes;strlencuenta los caracteres anteriores al terminador de una cadena.- Los elementos de un array ocupan almacenamiento consecutivo en orden de índice.
- No se garantizan las direcciones relativas de variables independientes.
- La alineación determina las direcciones iniciales adecuadas; el orden de los bytes determina su orden dentro de un valor.
- Una dirección guardada solo puede utilizarse mientras exista su objeto.
1.2 Almacenar y mostrar una dirección
Un puntero almacena una dirección y tiene un tipo que describe el objeto al que puede acceder. Declarar un int * te proporciona almacenamiento para un puntero; no crea un int que ese puntero pueda utilizar. Da al puntero un valor conocido cuando lo declares.
Obtener la dirección de un objeto
int count = 42;
int *p = &count;
El operador de obtención de dirección unario & obtiene la dirección de count. Si count comienza en la dirección ilustrativa 0x1000, p contiene 0x1000, mientras que count contiene 42. El puntero también es un objeto: &p es la dirección del almacenamiento independiente que contiene ese puntero.
Puedes obtener la dirección de un elemento de un array, como &scores[2]. Una constante numérica o el resultado de un cálculo no es un objeto almacenado cuya dirección puedas obtener de esta forma: &42 y &(count + 1) no son válidos. La expresión binaria a & b utiliza el mismo símbolo para la operación AND a nivel de bits; el número de operandos indica de qué operación se trata.
Interpretar cada declarador
| Declaración | Significado |
|---|---|
int *p, q; | Un puntero y un int ordinario |
int *p, *q; | Dos punteros a int |
double *r; | Un puntero a double |
Los caracteres en blanco no cambian ninguno de estos tipos. int* p e int *p son equivalentes. Declarar un puntero por línea hace más difícil pasar por alto su asterisco. En equipos de escritorio habituales de 64 bits, un puntero a objeto ocupa 8 bytes, pero su propio tamaño no indica el tamaño del objeto al que apunta.
El tipo apuntado importa. Para double price = 9.5;, utiliza double *r = &price;. Inicializar un int * con &price produce un mensaje de diagnóstico por punteros incompatibles. Una conversión explícita que oculte el mensaje no hace válido el acceso a ese double como int: el tipo determina cómo se accede a los bytes almacenados.
Redirigir un puntero
int a = 5, b = 8;
int *p = &a;
p = &b;
La última asignación reemplaza la dirección en p. No mueve ninguno de los enteros ni copia el valor de uno en el otro. Ahora p apunta a b; a sigue siendo 5 y b sigue siendo 8. Varias variables de tipo puntero también pueden almacenar la misma dirección.
Utilizar un estado vacío conocido
Un puntero local sin inicializar tiene un valor indeterminado, y a veces se llama puntero indeterminado. No lo examines ni lo desreferencies. Inicialízalo con la dirección de un objeto válido o con NULL, disponible en cabeceras como <stddef.h> y <stdio.h>:
int *pending = NULL;
if (pending == NULL) {
pending = &count;
}
Un puntero nulo no designa ningún objeto. !pending es otra prueba de nulidad. Estas pruebas solo tienen sentido para punteros inicializados; no validan una dirección arbitraria ni un puntero cuyo objeto ha dejado de existir.
Imprimir un puntero a objeto
printf("%p\n", (void *)p);
%p espera un void *, así que proporciona la conversión explícita cuando utilices un puntero a objeto con un tipo concreto. La implementación elige su representación impresa, que suele ser hexadecimal. %d y %x esperan argumentos enteros de tipos determinados y no pueden utilizarse como sustitutos de %p.
Puntos clave
- Inicializa un puntero con una dirección válida o con
NULL. &countes una dirección;countes el entero almacenado.- Cada nombre de puntero necesita su propio
*en una declaración. - Los tipos de puntero deben ser adecuados para los objetos a los que se accede mediante ellos.
p = &bcambia el puntero y deja los enteros sin modificar.- Imprime un puntero a objeto con
%py un argumentovoid *.
1.3 Seguir un puntero para leer o escribir
El operador de desreferenciación unario * sigue un puntero hasta su objeto. Si p apunta a x, *p designa el propio x. Leer *p lee ese objeto; asignar un valor a *p lo modifica.
Seguir una escritura mediante una dirección
int x = 10;
int *p = &x;
*p = *p + 5;
int y = *p * 2;
printf("%d %d %d\n", x, *p, y);
Primero p almacena la dirección de x. En el lado derecho de la asignación, *p lee 10. Al sumar 5 se obtiene 15, y el *p del lado izquierdo almacena 15 en x. La siguiente línea multiplica el valor actualizado por 2, por lo que y pasa a ser 30. La salida es 15 15 30.
El contexto distingue los asteriscos: int *p declara un puntero, el *p unario accede a un objeto y *p * 2 también contiene una multiplicación binaria. Para un objeto válido x, *&x designa x; para un puntero a un objeto válido, &*p devuelve el valor del puntero.
Distinguir las direcciones del contenido
Comienza cada traza con dos filas de información: qué dirección contiene cada puntero y qué valor contiene cada entero. Supón que p apunta a a, que contiene 3, y q apunta a b, que contiene 8.
| Sentencia considerada desde ese estado inicial | Efecto |
|---|---|
*p = *q; | Copia 8 en a; los punteros conservan sus destinos |
p = q; | Hace que p apunte a b; los enteros conservan sus valores |
Un diagrama de memoria utiliza una caja para cada objeto. Etiqueta una caja con su nombre y dirección, coloca dentro su valor actual y dibuja una flecha desde un puntero hasta su destino. Una asignación de dirección mueve una flecha; una asignación al objeto apuntado cambia el contenido de la caja de destino.
Después de p = q, los punteros son alias de b. Si luego ejecutas *p = 20;, leer *q o b da 20. Copiar una dirección crea otra vía hacia el mismo objeto, no otro entero.
Comprobar que una desreferenciación es válida
Un puntero utilizado para acceder debe designar un objeto existente de un tipo adecuado, y escribir también requiere un objeto modificable. Los punteros nulos no designan ningún objeto. Los punteros sin inicializar no tienen un destino conocido que se pueda utilizar. Un puntero queda colgante cuando termina el tiempo de vida de su destino.
Acceder mediante un puntero no válido es comportamiento indefinido: C no impone requisitos sobre el resultado. El programa puede detenerse con un fallo de segmentación, corromper datos o parecer que se ejecuta normalmente. Una ejecución aparentemente correcta no demuestra que el acceso fuera válido.
Para un puntero que puede ser nulo legítimamente, compruébalo antes de acceder:
if (p != NULL) {
*p = 0;
}
Asignar NULL a un puntero que ya no necesitas puede evitar que esa variable se utilice por accidente más adelante. No restablece otros alias. Lleva por separado el control del tiempo de vida del objeto.
Poner paréntesis alrededor del valor deseado
El ++ posfijo tiene mayor precedencia que el * unario. Por tanto, *p++ significa *(p++): utiliza el valor anterior del puntero para acceder a un valor y avanza el puntero. (*p)++ incrementa el valor apuntado y deja el puntero en su lugar. *p + 1 lee el valor y suma 1 sin almacenar el resultado. Son tres acciones distintas.
Puntos clave
*pdesigna el objeto al que se accede mediantep.- Copiar una dirección crea alias; copiar
*pcopia un valor. - Una desreferenciación requiere un objeto existente y un tipo de acceso adecuado.
- El comportamiento indefinido no garantiza ningún resultado, ni siquiera un cierre inesperado.
*p++avanza el puntero;(*p)++incrementa su destino.
2Permitir que las funciones trabajen con datos de la función llamadora
Una función recibe una copia de cada argumento, incluidos los argumentos de tipo puntero. Esa dirección copiada aún puede acceder a un objeto que pertenece a la función llamadora, lo que permite intercambiar valores, utilizar funciones de entrada y obtener varios resultados. La interfaz de la función también debe mostrar si modifica los datos. Sigue la dirección dentro de la llamada, distingue los cambios locales de las escrituras mediante ella y comprueba qué objetos seguirán existiendo cuando se utilice un puntero devuelto.
2.1 Modificar variables de la función llamadora mediante parámetros
C pasa todos los argumentos por valor. Un parámetro es una variable local inicializada a partir del argumento. Pasar una dirección no cambia esa regla: la función recibe una copia de la dirección y esa copia puede acceder al objeto de la función llamadora.
Comparar dos funciones de restablecimiento
void reset_copy(int n) {
n = 0;
}
void reset(int *n) {
*n = 0;
}
Para int score = 12;, reset_copy(score) cambia solo su n local. score sigue siendo 12. La llamada reset(&score) copia la dirección de score en el parámetro de tipo puntero n; asignar después un valor a *n cambia score a 0. Esta técnica suele llamarse paso por referencia, aunque el paso real de argumentos del lenguaje sigue siendo por valor.
La dirección en la llamada y la desreferenciación en el cuerpo cumplen funciones distintas. &score indica a la función dónde está el objeto. *n = 0 le indica qué almacenar allí. Llamar a reset(score) proporciona un entero donde se requiere un puntero y provoca un mensaje de diagnóstico del compilador.
Analizar un intercambio
void swap(int *a, int *b) {
int t = *a;
*a = *b;
*b = t;
}
/* Inside a calling function: */
int x = 3, y = 8;
swap(&x, &y);
aapunta axybapunta ay.tguarda el valor original dex, que es 3.*a = *bescribe 8 enx.*b = tescribe el 3 guardado eny.
Ambas variables de la función llamadora cambian. Una versión que recibe int a, int b normales solo intercambia copias. Una versión con punteros que hace int *t = a; a = b; b = t; tampoco intercambia los enteros: solo redirige los parámetros locales de tipo puntero.
Entregar más de un resultado
Una sentencia de retorno proporciona un valor, pero los parámetros de tipo puntero pueden llevar resultados adicionales:
void divide(int total, int size, int *q, int *r) {
*q = total / size;
*r = total % size;
}
/* size must be positive for these example inputs. */
int q, r;
divide(17, 5, &q, &r);
La división entera da q igual a 3 y r igual a 2. La función llamadora proporciona dos objetos modificables distintos. Otra interfaz útil devuelve un estado de éxito y escribe un resultado solo si tiene éxito; las funciones llamadoras comprueban ese estado antes de utilizar el resultado.
Reconocer el mismo patrón en llamadas a la biblioteca
scanf("%d", &age) necesita la dirección de age porque almacena allí el entero leído. Pasar age proporciona un valor en lugar del int * requerido, lo que produce comportamiento indefinido. Comprueba que scanf devuelva 1 antes de utilizar el valor recién leído.
Para texto, char name[20]; scanf("%19s", name); pasa un puntero al primer carácter del array. El ancho permite como máximo 19 caracteres de entrada y deja espacio para '\0'. La expresión del array ya proporciona el puntero a carácter requerido.
Un parámetro de tipo array también recibe una dirección, no un bloque copiado de elementos. En void zero(int *a, size_t n), las asignaciones a a[i] afectan al array de la función llamadora. Pasar la dirección solo copia un puntero, lo que también permite que funciones de solo lectura examinen arrays grandes sin copiar cada elemento.
Puntos clave
- C copia todos los argumentos, incluidas las direcciones.
- Una escritura mediante un parámetro de tipo puntero puede modificar el objeto de la función llamadora.
- Un intercambio de enteros que funciona intercambia
*ay*butilizando un valor guardado. - Los parámetros de salida necesitan objetos válidos y modificables proporcionados por la función llamadora.
scanfrecibe direcciones e indica cuántas conversiones tuvieron éxito.- Los parámetros de tipo array comparten el acceso a los elementos de la función llamadora.
2.2 Acceso con const y valores de retorno seguros
Una interfaz con punteros debe dejar claras dos cosas: si la función puede escribir mediante el puntero y durante cuánto tiempo sigue siendo válido el almacenamiento apuntado. const responde a la primera pregunta. El tiempo de vida del objeto responde a la segunda.
Identificar la restricción de const
| Declaración | ¿Puede redirigirse p? | ¿Puede asignarse un valor mediante *p? |
|---|---|---|
const int *p = &x; | Sí | No |
int *const p = &x; | No | Sí |
const int *const p = &x; | No | No |
Un puntero a datos constantes limita esta vía de acceso. Si x se definió como un int x = 5; ordinario, x = 6; sigue estando permitido aunque un const int * apunte a él. Si el propio objeto se definió con const, intentar modificarlo mediante cualquier puntero es comportamiento indefinido.
Un puntero constante conserva una dirección. Debe inicializarse y la dirección no puede sustituirse después. El objeto en esa dirección aún puede cambiar cuando su tipo lo permite. Lee hacia fuera desde el nombre: int *const p hace que p sea constante; const int *p protege el int al que se accede mediante p.
Utiliza un parámetro de solo lectura como const char *s cuando una función solo examina una cadena. Puede pasarse un char * ordinario a ese parámetro sin conversión explícita. La función puede avanzar su s local, pero se rechaza la asignación mediante *s. Volver a convertir a un tipo modificable requiere más que eliminar una advertencia del compilador: el objeto subyacente debe ser realmente modificable.
Devolver una posición en almacenamiento de la función llamadora
int *find(int *a, size_t n, int target) {
for (size_t i = 0; i < n; i++) {
if (a[i] == target) return &a[i];
}
return NULL;
}
Para int v[] = {4, 9, 2, 9};, find(v, 4, 9) devuelve &v[1]. La función termina, pero v pertenece a la función llamadora y sigue existiendo allí. Después de comprobar el resultado frente a NULL, la función llamadora puede calcular found - v para obtener el índice 1 o asignar *found = 0 para actualizar el elemento. Una búsqueda de 7 devuelve NULL, que no debe desreferenciarse. strchr y strstr usan la misma convención de devolver un puntero nulo cuando no se encuentra el resultado.
Rechazar direcciones de variables locales cuyo tiempo de vida ha terminado
int *bad(void) {
int result = 42;
return &result;
}
result tiene duración automática del almacenamiento. Su tiempo de vida termina cuando la llamada retorna, lo que deja a la función llamadora con un puntero colgante. El compilador puede advertir de ello y cualquier acceso posterior mediante ese puntero tiene comportamiento indefinido. Que los bytes antiguos sean visibles en una ejecución no mantiene vivo el objeto.
El mismo problema puede ocurrir en la llave de cierre de un bloque interior. Un puntero exterior no puede prolongar el tiempo de vida de una variable interior cuya dirección guardó.
Elegir almacenamiento que dure lo suficiente
Devuelve un valor int cuando la función llamadora solo necesite un resultado entero. Para varios resultados, deja que la función llamadora proporcione el almacenamiento que se va a rellenar. Un puntero devuelto también puede referirse al array de la función llamadora, a un literal de cadena expuesto mediante const char * o a un objeto static.
Una variable local estática se inicializa una vez y dura todo el programa, pero todas las llamadas la comparten. Si una llamada posterior almacena allí un nuevo valor, todos los punteros devueltos anteriormente ven ese cambio. El almacenamiento estático resuelve el problema del tiempo de vida sin proporcionar a cada llamada su propio objeto de resultado independiente.
Puntos clave
const int *restringe las escrituras mediante ese puntero, mientras queint *constfija la dirección.- Un objeto definido con
constno puede modificarse de forma válida. - Los punteros a elementos de un array existente de la función llamadora pueden seguir siendo válidos después de que retorne una función.
- Comprueba un resultado de búsqueda que pueda ser nulo antes de acceder a él o restarlo.
- Devolver la dirección de una variable local automática deja un puntero colgante.
- Un objeto estático de resultado se comparte entre todas las llamadas.
3Recorrer arrays y cadenas
La aritmética de punteros relaciona la dirección de un elemento con su posición en un array. El tipo apuntado proporciona el tamaño del paso, mientras que el array proporciona el rango permitido. Utiliza ese modelo para interpretar índices, pasar un segmento a una función y construir bucles que se detengan antes de un acceso no válido. Las cadenas añaden una segunda regla de parada: su carácter nulo de terminación marca el final del texto, incluso cuando el almacenamiento contiene más bytes.
3.1 Pasos entre elementos y límites del array
Sumar un entero a un puntero lo desplaza por elementos del tipo apuntado. Si p apunta al elemento i de un array, p + n apunta al elemento i + n, siempre que la posición resultante esté dentro del array o inmediatamente después de su último elemento.
Ajustar la dirección una sola vez
En la dirección ilustrativa 0x1000, sumar 3 da estos resultados:
| Tipo de puntero | Tamaño de un elemento | Dirección después de p + 3 |
|---|---|---|
int * | 4 bytes | 0x100C |
double * | 8 bytes | 0x1018 |
char * | 1 byte | 0x1003 |
El compilador realiza el ajuste según el tamaño. Escribir p + 3 * sizeof *p lo aplica dos veces y generalmente llega al elemento equivocado. Utiliza unsigned char * cuando el paso deseado sea realmente un byte de la representación de un objeto.
Para int a[6] = {10, 20, 30, 40, 50, 60}; e int *p = &a[1];, *(p + 2) lee a[3], que es 40. La asignación p += 3; desplaza el propio puntero a a[4], que contiene 50. Desde ahí, *(p - 1) lee 40. p++, p--, p += n y p -= n se desplazan por elementos.
Restar posiciones para contar elementos
&a[5] - &a[1] es 4, e invertir los operandos da -4. El resultado ya cuenta elementos; no lo dividas por sizeof(int). Su tipo con signo es ptrdiff_t, declarado en <stddef.h>, y %td lo imprime.
Resta solo punteros al mismo array, incluida la posición inmediatamente posterior a su último elemento, y con un resultado representable en ptrdiff_t. Esta operación describe posiciones dentro de ese array. Restar direcciones de variables no relacionadas es comportamiento indefinido, aunque los números impresos estén casualmente próximos.
Utilizar la posición posterior al final como límite
Para seis elementos, a + 6 es un puntero a la posición inmediatamente posterior al último elemento válido. Puede servir como límite y participar en una resta válida dentro del mismo array, pero no puede desreferenciarse. a + 7 y a - 1 ya tienen comportamiento indefinido al formarse; evitar una desreferenciación posterior no los corrige.
for (int *p = a; p < a + 6; p++) {
printf("%d\n", *p);
}
El cuerpo se ejecuta para los índices de 0 a 5. Después del último incremento, p es igual a a + 6 y la siguiente prueba detiene el bucle. Utilizar <= permitiría que el cuerpo accediera a esa posición que no es un elemento.
Para las reglas de la aritmética, un objeto individual actúa como un array de un elemento. Puedes formar &x + 1; no puedes utilizarlo para leer una supuesta variable siguiente.
Comparar solo posiciones pertinentes
Los operadores de orden como < comparan posiciones dentro de un array. No ordenes punteros a objetos no relacionados. La igualdad y la desigualdad pueden comparar tipos de puntero compatibles y comprobar si un puntero es nulo.
No puedes sumar dos punteros, multiplicar punteros ni realizar aritmética de C estándar con un void * o un puntero a función. Para encontrar el punto medio de un intervalo [p, q) dentro de un mismo array, utiliza p + (q - p) / 2: resta las posiciones para obtener una cantidad, divide esa cantidad y luego avanza desde p.
Puntos clave
- Sumar un entero desplaza un puntero por elementos de su tipo apuntado.
- Una diferencia entre punteros al mismo array ya cuenta elementos y tiene tipo
ptrdiff_t. - Los punteros a la posición posterior al final pueden marcar un límite, pero no pueden desreferenciarse.
- Formar un puntero anterior al array o más allá de la posición posterior al final no es válido.
- Utiliza posiciones del mismo array para las comparaciones de orden.
- Resta las posiciones antes de calcular un punto medio.
3.2 Expresiones de array y parámetros de tipo puntero
Un array es un bloque de elementos. Un puntero es un objeto independiente que contiene una dirección. Su estrecha relación procede de una conversión automática que ocurre en la mayoría de las expresiones, no de que los dos objetos sean del mismo tipo.
Convertir una expresión de array
int a[5] = {3, 1, 4, 1, 5};
int *p = a;
Aquí a se convierte en un puntero a a[0]. Esta conversión de array a puntero hace que el inicializador sea equivalente a &a[0]. Tanto *p como a[0] leen 3.
C define el acceso mediante índices a través de la aritmética: a[i] significa *(a + i). Por tanto, p[3] también funciona y lee 1. &a[i] y a + i identifican el mismo elemento. Incluso 3[a] tiene el mismo significado definido que a[3], aunque la forma habitual es mucho más clara.
Saber cuándo el array completo sigue siendo un array
La conversión no ocurre para los operandos de sizeof o del & unario. Tampoco ocurre para typeof y typeof_unqual de C23, ni cuando un literal de cadena inicializa un array de caracteres.
Expresión, con valores int de 4 bytes y un puntero de 8 bytes | Resultado |
|---|---|
sizeof a | 20 bytes para los cinco elementos |
sizeof p | 8 bytes para el objeto de tipo puntero |
sizeof a / sizeof a[0] | 5 elementos |
No puedes asignar una nueva dirección a un array ni incrementar su nombre: a = p; y a++; no son válidos. Puedes redirigir p, por ejemplo, con p = a + 2;. Copia el contenido del array con un bucle sobre los elementos o con una llamada adecuada a memcpy.
Seguir el tipo ajustado del parámetro
int sum(const int *values, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) total += values[i];
return total;
}
La función llamadora proporciona tanto una dirección como una cantidad. Para el array anterior, sum(a + 2, 3) visita los índices originales 2, 3 y 4, y suma 4, 1 y 5 para devolver 10. Dentro de la función, el índice 0 es relativo al puntero inicial proporcionado.
En una lista de parámetros de función, int a[], int a[10] e int *a declaran un parámetro de tipo puntero. La forma simple [10] no proporciona un límite comprobado ni transmite una longitud. Dentro de una función así, sizeof a mide un puntero. La expresión sizeof a / sizeof a[0] daría 2 en un sistema con punteros de 8 bytes y valores int de 4 bytes, independientemente de la longitud real del array de la función llamadora.
Distinguir un puntero a elemento de un puntero a array
a se convierte en int *, mientras que &a tiene el tipo int (*)[5], un puntero al array completo de cinco elementos. Ambos identifican el comienzo del mismo almacenamiento, pero la aritmética sigue sus distintos tipos. a + 1 avanza un int, o 4 bytes en esta disposición. &a + 1 avanza un array completo, o 20 bytes, hasta su posición inmediatamente posterior al final.
Una dirección inicial impresa no puede revelar esa diferencia de tipos. Lee la declaración y mantén el tipo apuntado junto al puntero al seguir una expresión.
Puntos clave
- La mayoría de las expresiones de array se convierten en un puntero al primer elemento.
a[i]se define como*(a + i).sizeof amide el array completo solo dondeaes realmente un array.- Un parámetro de tipo array se ajusta a un puntero; pasa su longitud utilizable por separado.
&aapunta a un array completo, por lo que su aritmética tiene un paso mayor.
3.3 Bucles con punteros sobre números y texto
Un bucle con puntero necesita una posición actual y una regla de parada. Un array con un número conocido de elementos utiliza un puntero al final; una cadena utiliza su '\0' de terminación. Elige la forma que permita ver con mayor facilidad los elementos visitados.
Recorrer un intervalo de longitud conocida
int a[] = {3, 1, 4, 1, 5};
int total = 0;
for (const int *p = a; p < a + 5; p++) {
total += *p;
}
Los totales acumulados son 3, 4, 8, 9 y 14. Un bucle con índice que suma a[i] para índices menores que 5 da el mismo resultado. Los compiladores optimizadores modernos suelen generar código similar para estas formas; la sintaxis de punteros por sí sola no garantiza ninguna ventaja de velocidad.
Leer antes o después de avanzar
Con p en el 3 de {3, 1, 4}, int v = *p++; asigna 3 a v y deja p en el 1. El incremento posfijo proporciona el valor anterior del puntero a * y actualiza el puntero. *++p primero avanza al siguiente elemento y luego lo lee. (*p)++ modifica el elemento actual y no desplaza el puntero.
Detenerse en el terminador de una cadena
size_t my_strlen(const char *s) {
const char *p = s;
while (*p != '\0') p++;
return (size_t)(p - s);
}
Para "pointer", p se detiene en el terminador, siete posiciones desde el inicio. El resultado excluye ese terminador. Un recuento de caracteres utiliza el mismo recorrido: examinar "mississippi" e incrementar un contador cada vez que *p == 's' cuenta cuatro coincidencias entre once letras.
Un puntero al interior de una cadena da acceso a su sufijo. Con const char *msg = "hello world";, puts(msg + 6) imprime "world". Los caracteres anteriores permanecen donde estaban.
Copiar también el terminador
char destination[4];
const char *src = "cat";
char *dst = destination;
while ((*dst++ = *src++) != '\0') {
}
Cada iteración asigna un carácter, comprueba el valor asignado y avanza ambos punteros. La última iteración copia '\0', por lo que el destino es una cadena completa. Ambos punteros quedan entonces justo después de sus terminadores. El almacenamiento necesita la longitud del texto más un byte; omitir ese byte adicional provoca un desbordamiento de búfer. Este patrón de copia también requiere que las regiones de origen y destino no se solapen.
Invertir desde ambos extremos
Para una cadena modificable, obtén primero su longitud. Si la longitud es cero, detente antes de formar un puntero al último carácter. De lo contrario, establece left = s y right = s + length - 1. Mientras left < right, intercambia los caracteres, incrementa left y decrementa right. Para "stressed", cuatro intercambios producen "desserts"; el terminador permanece en el extremo original.
La comprobación de cadena vacía importa porque restar 1 de su puntero inicial formaría un puntero anterior al array.
Elegir almacenamiento de caracteres modificable
char word[] = "hello"; crea un array modificable de seis bytes, incluido el terminador. const char *word = "hello"; apunta a un literal de cadena con duración estática del almacenamiento. Modificar un literal de cadena es comportamiento indefinido, incluso si una declaración utiliza char * sin const. Utiliza const char * para que las escrituras accidentales sean un error de compilación.
Un bucle sobre un array debe detenerse antes de la posición inmediatamente posterior a su final, y un recorrido de cadena debe encontrar realmente un terminador dentro del almacenamiento accesible. Un puntero no lleva consigo ni una longitud ni una garantía de que los bytes formen una cadena.
Puntos clave
- Un intervalo de longitud conocida termina en un puntero a la posición posterior al final; una cadena termina en su carácter nulo.
*p++utiliza la posición anterior y avanza el puntero.- La copia de cadenas incluye el terminador y necesita tantos bytes como la longitud más uno.
- Comprueba si una cadena está vacía antes de formar su puntero al último carácter.
- Los arrays de caracteres inicializados con texto son modificables; los literales de cadena no lo son.
- Un puntero por sí solo no proporciona una longitud ni garantiza la terminación.
4Seguir y actualizar otro puntero
Un puntero puede ser a su vez el objeto al que se accede mediante una dirección. Seguir dos niveles permite que una función sustituya el puntero de la función llamadora y que un programa recorra un array de direcciones de cadenas. Ten presente el tipo de cada nivel: un puntero a un entero, un puntero a ese puntero y un puntero a una fila completa representan disposiciones distintas. Sigue el objeto al que llega cada desreferenciación antes de determinar qué modificará una asignación.
4.1 Dos niveles de indirección
Un puntero es un objeto almacenado, por lo que tiene una dirección que otro puntero puede contener. Un puntero a puntero añade un paso más entre la expresión y los datos finales.
Seguir las flechas de una en una
int x = 7;
int *p = &x;
int **pp = &p;
Utiliza direcciones ilustrativas: x en 0x2000 contiene 7; p en 0x3000 contiene 0x2000; pp en 0x4000 contiene 0x3000. Al evaluar *pp se llega a p, cuyo valor tiene tipo int *. Al evaluar **pp se sigue ese puntero hasta x, un int.
| Expresión | Tipo | Objeto o valor alcanzado |
|---|---|---|
pp | int ** | Dirección de p |
*pp | int * | El puntero almacenado en p |
**pp | int | El entero almacenado en x |
&p tiene un nivel de puntero más que p; cada desreferenciación elimina un nivel. Inicializar int **pp con &x produce una incompatibilidad porque &x tiene tipo int *.
Elegir el nivel de asignación
Con otro entero existente y, **pp = 9; modifica x. La asignación *pp = &y; modifica el puntero p, por lo que los usos posteriores de **pp llegan a y. Deja x con su valor actual. Si q es otro int *, pp = &q; modifica solo el puntero exterior, haciendo que su primera flecha llegue a q en lugar de p.
No cuentes los asteriscos sin comprobar también el objeto de cada nivel. Antes de evaluar **pp, tanto la ruta hasta el objeto de tipo puntero como la ruta desde ese objeto hasta el entero deben ser válidas.
Desplazar un puntero a cadena de la función llamadora
void skip_spaces(const char **s) {
while (**s == ' ') {
(*s)++;
}
}
/* In the caller: */
const char *text = " hi";
skip_spaces(&text);
puts(text);
La función llamadora pasa la dirección de su variable de tipo puntero. Por tanto, *s designa text; **s lee el carácter actual. Cada (*s)++ avanza text un carácter. Después de tres iteraciones apunta a 'h' y puts imprime "hi". La función requiere un puntero válido a un puntero que apunte a una cadena terminada en nulo.
Una versión que recibe const char *s e incrementa s modifica solo una copia local. Los paréntesis en (*s)++ también son imprescindibles: *s++ incrementa el puntero local exterior en lugar del puntero a carácter de la función llamadora. En const char **s, los caracteres son de solo lectura mediante esta vía de acceso, pero el puntero de la función llamadora puede desplazarse.
Leer un puntero de salida de la biblioteca
strtol de <stdlib.h> utiliza un parámetro de salida de tipo char **:
char *end;
long number = strtol("42abc", &end, 10);
Devuelve 42 y almacena en end un puntero al primer carácter no utilizado, que ahora apunta a "abc". Si el puntero al final es igual al puntero inicial, no se convirtió ningún dígito. Un analizador numérico que realice todas las comprobaciones también gestiona los errores de rango; aquí la función del puntero es indicar la posición de parada.
Puntos clave
int **pppuede contener la dirección de un objeto de tipoint *.- Cada desreferenciación sigue un nivel de puntero.
**ppmodifica el entero final;*pppuede modificar el puntero intermedio.- Actualizar un puntero de la función llamadora requiere la dirección de ese puntero.
strtolutilizachar **para devolver la posición del primer carácter no utilizado.
4.2 Arrays de punteros a cadenas y arrays rectangulares
Un array puede almacenar punteros como elementos. Un array bidimensional, en cambio, almacena filas completas como elementos. Ambos admiten dos índices, pero su almacenamiento y los tipos de puntero a los que se convierten son distintos.
Almacenar direcciones de cadenas independientes
const char *days[] = {"Mon", "Tue", "Wed"};
days contiene tres punteros a caracteres. Cada uno apunta a un literal de cadena independiente. days[1] es el puntero a "Tue", y days[1][0] lee 'T'. Con punteros a objetos de 8 bytes, sizeof days es 24, independientemente de las longitudes de las cadenas. Las cadenas ocupan almacenamiento en otro lugar.
Las longitudes pueden diferir, lo que produce una colección irregular. Reordenar la lista puede intercambiar elementos de tipo puntero sin copiar el texto. Asignar days[0] = "Thursday"; está permitido: el elemento de tipo puntero puede cambiar aunque los caracteres a los que se accede mediante él sean de solo lectura.
En la mayoría de las expresiones, days se convierte en un puntero a su primer elemento. Como un elemento es un const char *, el tipo convertido es const char **:
for (const char **d = days; d < days + 3; d++) {
puts(*d);
}
El bucle avanza un elemento de tipo puntero por iteración. *d obtiene el puntero a carácter que necesita puts.
Leer cadenas de la línea de comandos
int main(int argc, char *argv[]) recibe el mismo tipo de parámetro que char **argv. argc no es negativo y cuenta las cadenas disponibles. Cuando es positivo, argv[0] representa el nombre del programa o una cadena vacía si ese nombre no está disponible. Los argumentos del usuario ocupan los índices desde 1 hasta argc - 1, y argv[argc] es un puntero nulo.
En una invocación típica ./greet Ada 42, argc es 3, argv[1] apunta a "Ada" y argv[2] apunta a "42". El último argumento es texto hasta que lo lee una conversión como strtol. argv[1][0] es 'A'. Comprueba argc > 2 antes de utilizar argv[2].
Un bucle que imprime los argumentos del usuario puede utilizar for (int i = 1; i < argc; i++) puts(argv[i]);. Para recorrer desde argv + 1 hasta un puntero nulo, comprueba primero argc > 0; si argc es cero, no hay un intervalo de argumentos del usuario que pueda comenzarse de ese modo. Las cadenas de argumentos son modificables, a diferencia de los literales de cadena.
Seguir filas completas
int grid[3][4] contiene doce enteros en orden por filas: toda la fila 0, luego la fila 1 y después la fila 2. Con valores int de 4 bytes, cada fila ocupa 16 bytes. grid[2][1] está 36 bytes después de grid[0][0]: dos filas completas aportan 32 bytes y la columna aporta 4.
| Declaración o expresión | Significado |
|---|---|
int (*rows)[4] | Puntero a una fila de cuatro enteros |
int *ptrs[4] | Array de cuatro punteros a enteros |
grid convertido | int (*)[4] |
grid + 1 avanza una fila. Una función que lo recibe puede utilizar int g[][4] o int (*g)[4], con un número de filas separado. No puede utilizar int **: la tabla no tiene punteros a filas almacenados que puedan desreferenciarse. Una conversión explícita no puede crear esos punteros inexistentes.
Del mismo modo, char names[3][6] dispone de dieciocho posiciones para caracteres en filas de tamaño fijo. const char *names[3] dispone de tres posiciones para punteros que pueden referirse a cadenas de distintas longitudes.
Puntos clave
- Un array de punteros a cadenas almacena direcciones, con el texto en otro lugar.
- Un array de punteros se convierte en un puntero a su primer elemento de tipo puntero.
argv[argc]es nulo; comprueba el número de argumentos antes de leer una cadena concreta.- Un array bidimensional almacena filas contiguas y no contiene objetos de tipo puntero a fila.
int (*)[4]eint **describen disposiciones distintas de almacenamiento.- Dos índices por sí solos no demuestran que dos objetos tengan el mismo tipo.
5Elegir funciones y proporcionar funciones de devolución de llamada
Un puntero a función permite que un programa elija una operación durante su ejecución. Su tipo recoge la llamada permitida y un parámetro de función de devolución de llamada permite que otra función solicite esa operación cuando la necesite. Utiliza estas ideas primero en una pequeña tabla de funciones aritméticas y después en qsort, donde la función de devolución de llamada debe interpretar correctamente las direcciones de los elementos y devolver un orden coherente. Ordenar punteros a cadenas reúne tanto los punteros a funciones como dos niveles de punteros a objetos.
5.1 Declaraciones y llamadas de punteros a funciones
Un puntero a función almacena una referencia a una función a la que se puede llamar más adelante. Su tipo incluye el tipo de retorno y los tipos de los parámetros. Esos tipos deben coincidir con los de la función utilizada mediante el puntero.
Poner paréntesis alrededor del nombre del puntero
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int (*op)(int, int) = add;
Lee desde op: es un puntero, aquello a lo que apunta es una función que recibe dos valores int, y esa función devuelve un int. Sin los paréntesis, int *op(int, int); declara una función que devuelve un puntero a entero. Los paréntesis cambian el significado de la declaración.
El nombre de una función se convierte en un puntero a función en este inicializador. op = &add; es equivalente a op = add;. Tanto op(6, 3) como (*op)(6, 3) llaman a add y devuelven 9. Asignar op = sub; hace que la misma llamada devuelva 3. En la expresión de llamada no ha cambiado nada salvo la función almacenada en el puntero.
Mantener la firma compatible
Un puntero de tipo int (*)(int, int) no puede llamar de forma segura a una función que recibe y devuelve double. Por ejemplo, asignarle sqrt produce un mensaje de diagnóstico por tipos incompatibles. Convertir explícitamente el puntero a función para eliminar un mensaje de diagnóstico no hace válida una llamada incompatible; el comportamiento es indefinido.
C estándar trata por separado los punteros a objetos y los punteros a funciones. void * es el tipo de puntero genérico a objeto, no un contenedor portable para un puntero a función. Los punteros a funciones admiten pruebas de igualdad y desigualdad, pero no aritmética de punteros.
Un puntero nulo a función significa que no hay ninguna función a la que llamar. Cuando una interfaz permite ese estado, compruébalo:
if (op != NULL) {
int result = op(6, 3);
printf("%d\n", result);
}
Llamar mediante un puntero nulo a función es comportamiento indefinido.
Dar nombre al tipo una vez
typedef int (*binop)(int, int);
binop ops[] = {add, sub, mul};
El typedef da nombre a un tipo de puntero. binop selected = mul; declara un puntero; binop pick(char symbol); declara una función que devuelve uno. Sin el typedef, esa declaración de retorno sería int (*pick(char symbol))(int, int);.
Un array de punteros a funciones es una tabla de despacho. ops[2](6, 3) selecciona mul y devuelve 18. Para un índice de selección con signo i, comprueba i >= 0 && i < 3 antes de evaluar ops[i]. Acceder fuera de los límites de la tabla no es válido aunque se pueda llamar a todas las funciones almacenadas.
Interpretar declaraciones más amplias hacia fuera
Comienza por el nombre y resuelve los paréntesis y las partes posfijas antes de avanzar más hacia fuera. En int (*table[3])(int, int), table[3] forma un array de tres elementos, * hace que cada elemento sea un puntero y (int, int) hace que su destino sea una función que devuelve int.
En char *(*f)(const char *), f es un puntero a una función que recibe un const char *; esa función devuelve un char *. Mantén separados los requisitos de tiempo de vida del puntero devuelto y los requisitos de compatibilidad de llamada del puntero a función.
Puntos clave
- Los paréntesis distinguen un puntero a función de una función que devuelve un puntero.
- Un puntero a función recoge los tipos de retorno y de parámetros de las llamadas válidas.
- El nombre de una función puede inicializar un puntero a función compatible sin
&. - Un typedef puede hacer más fáciles de leer las tablas y los tipos de retorno de punteros a funciones.
- Valida el índice de la tabla y cualquier puntero que pueda ser nulo antes de llamar.
5.2 Funciones de devolución de llamada y comparaciones correctas para qsort
Una función de devolución de llamada es una función proporcionada a otra función, que la llama como parte de su trabajo. Esto separa la operación general de recorrido u ordenación de la acción o el orden concreto que elijas.
Pasar una operación sin llamarla
void twice(int *x) { *x *= 2; }
void for_each(int *a, size_t n, void (*fn)(int *)) {
for (size_t i = 0; i < n; i++) fn(&a[i]);
}
Para int v[] = {1, 2, 3};, for_each(v, 3, twice); produce {2, 4, 6}. El nombre de la función proporciona un puntero. Escribir twice() intentaría llamarla de inmediato, sin el argumento requerido, en lugar de proporcionar una operación para llamadas posteriores.
Recuperar un tipo desde un puntero genérico a objeto
Un void * puede contener un puntero a objeto y convertirse de nuevo al tipo original. No puede desreferenciarse ni utilizarse en aritmética de punteros de C estándar porque no proporciona un tipo de elemento. Conserva los calificadores: los datos de solo lectura pasan por const void * y después por un puntero adecuado a datos constantes.
qsort de <stdlib.h> recibe una dirección base, un número de elementos, un tamaño de elemento en bytes y una función de comparación. La función de devolución de llamada tiene tipo int (*)(const void *, const void *). Cada argumento apunta a un elemento del array y el signo devuelto indica dónde corresponde el primer elemento:
| Signo devuelto | Orden |
|---|---|
| Negativo | El primero antes del segundo |
| Cero | Equivalentes para este orden |
| Positivo | El primero después del segundo |
Ordenar enteros sin desbordamiento
int cmp_int(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
/* In the caller: */
int v[] = {42, -7, 19, 0, 3};
qsort(v, 5, sizeof v[0], cmp_int);
El array pasa a ser {-7, 0, 3, 19, 42}. Las comparaciones producen 0 o 1, por lo que la expresión de retorno solo produce -1, 0 o 1. Invertir la expresión de orden a (y > x) - (y < x) ordena en sentido descendente.
No utilices return x - y; para enteros con signo arbitrarios. Para un int de 32 bits, restar -2 000 000 000 de 2 000 000 000 supera INT_MAX. El desbordamiento con signo tiene comportamiento indefinido, por lo que no se garantiza un resultado reducido modularmente ni un signo de comparación fiable.
Ordenar punteros a cadenas
int cmp_str(const void *a, const void *b) {
return strcmp(*(const char *const *)a,
*(const char *const *)b);
}
const char *names[] = {"Lin", "Ada", "Grace"};
qsort(names, 3, sizeof names[0], cmp_str);
Incluye <string.h> para strcmp. Aquí un elemento del array es a su vez un puntero. La función de devolución de llamada recibe una dirección de ese elemento de tipo puntero, por lo que desreferenciar una vez obtiene la dirección de la cadena. El const interior añadido impide modificar el elemento mediante esta vía de acceso. Después de ordenar, el orden es "Ada", "Grace", "Lin".
strcmp(a, b) leería los bytes de las posiciones de puntero como texto. Utiliza también sizeof names[0], el tamaño de un elemento de tipo puntero, no sizeof(char) ni la longitud de un nombre.
Las funciones de comparación deben dejar los elementos sin cambios y responder de forma coherente, incluido el orden transitivo. qsort no garantiza conservar el orden de los elementos que se consideran iguales al compararlos. bsearch acepta el mismo tipo de función de comparación para buscar en un array ya ordenado según ese criterio.
Puntos clave
- Pasa el nombre de una función para proporcionar una función de devolución de llamada; los paréntesis llaman a una función inmediatamente.
- Recupera el tipo de elemento a partir de cada argumento genérico de comparación antes de acceder.
qsortnecesita un número de elementos, el tamaño de un elemento y una función de comparación compatible.- Utiliza los signos de las comparaciones sin una resta que pueda desbordarse.
- Las funciones de comparación de punteros a cadenas reciben direcciones de posiciones de puntero.
- Los resultados de comparación iguales no garantizan un orden estable después de
qsort.
Ejemplos de Punteros y direcciones de memoria en C
1Seguir una dirección y el valor al que llega
13 pasos
- 1IntroducciónDistinguir tres cosas
Un entero almacenado tiene un valor, una ubicación y un tiempo de vida. Un puntero añade otro objeto cuyo valor es una dirección. Sigue un programa pequeño registrando ambos tipos de valores: enteros como 10 y direcciones como
0x1000.La pregunta útil en cada línea es concreta: ¿esta sentencia cambia una dirección, cambia el objeto al que se llega mediante esa dirección o simplemente lee un valor?
- 2ExplicaciónDistribuir cuatro enteros
Imagina que
int scores[4]comienza en0x1000en un sistema con enteros de 4 bytes. El primer elemento utiliza los bytes de0x1000a0x1003. El siguiente comienza en0x1004, seguido de0x1008y0x100C.Los elementos ocupan almacenamiento consecutivo. Esta regla de disposición pertenece al array; no garantiza ningún orden para dos enteros declarados por separado.
- 3EjemploMedir el array y después su contenido
Para
char word[20] = "hi";, dos mediciones dan resultados distintos:sizeof wordes 20 bytes de almacenamiento del array.strlen(word)es 2 caracteres antes de'\0'.
El terminador ocupa otro elemento del array y los elementos restantes siguen perteneciendo al array.
sizeofdevuelvesize_t, que se imprime con%zu; no cuenta cuántos elementos utiliza actualmente el programa. - 4ConexiónDistinguir la disposición del tiempo de vida
La alineación determina las direcciones iniciales adecuadas, mientras que el orden de los bytes determina su orden dentro de un valor. Ninguno de los dos indica cuánto tiempo existe un objeto.
Una variable local ordinaria dura mientras se ejecuta su bloque. Una variable local estática o global dura todo el programa. Guardar una dirección registra una ruta hacia el almacenamiento; no puede mantener activa una variable local automática después de que termine el bloque.
- 5DesafíoEncontrar la dirección de un elemento
Un array de enteros de seis elementos comienza en
0x1800, con 4 bytes por elemento. ¿Dónde comienza el elemento 3 y cuál es el tamaño de todo el array?---reveal---
El elemento 3 comienza doce bytes después del inicio, en
0x180C. Seis elementos de 4 bytes ocupan 24 bytes. El índice da un desplazamiento en elementos; multiplicarlo por el tamaño del elemento da el desplazamiento en bytes. - 6ExplicaciónDar un destino al puntero
Considera
int count = 42; int *p = &count;. El operador&obtiene la dirección decountypalmacena esa dirección. Sicountestá en la dirección ilustrativa0x2000, el valor del puntero es0x2000; el valor del entero sigue siendo 42.El puntero también ocupa su propio almacenamiento. Su dirección es
&p, una información distinta de la dirección contenida enp. - 7ConsejoInterpretar el asterisco junto a cada nombre
int *p, q;declara un puntero y un entero ordinario. Para declarar dos punteros, utilizaint *p, *q;o coloca las declaraciones en líneas separadas.Inicializa un puntero con la dirección de un objeto adecuado o con
NULL. Un puntero nulo es un estado vacío conocido; un puntero local sin inicializar tiene un valor indeterminado y no puede comprobarse de forma segura como si ya fuera nulo. - 8DesafíoRedirigir sin modificar los datos
Comienza con
int a = 9, b = 16; int *p = &a;. Después ejecutap = &b;. ¿Qué valores enteros cambian y qué contiene ahorap?---reveal---
Ninguno de los enteros cambia:
asigue siendo 9 ybsigue siendo 16. Solo cambiap, que almacena la dirección deb. La asignación de un puntero sustituye una dirección; no copia el contenido del destino anterior ni del nuevo. - 9EjemploLeer, sumar y escribir mediante p
Continúa con
int x = 10; int *p = &x;y ejecuta:*p = *p + 5;El
*pdel lado derecho lee 10 dex. Al sumar 5 se obtiene 15. El*pdel lado izquierdo designa el mismo objeto, por lo que la asignación escribe 15 enx. La dirección depno ha cambiado. Después,int y = *p * 2;lee 15 y crea un entero independienteyque contiene 30. - 10Idea claveUna copia de la dirección crea otra ruta
Para
int n = 6; int *p = &n; int *q = p;, ambos punteros llegan an. La asignación copió una dirección, no el objeto entero.Después de
*q = 14;, leern,*po*qda 14. Dibuja una caja para el entero y dos flechas hacia ella. Crear dos cajas para enteros sugeriría incorrectamente que son datos independientes. - 11ConsejoComprobar el destino antes de acceder
El acceso necesita un puntero a un objeto existente de un tipo adecuado. Un puntero nulo no designa ningún objeto y un puntero colgante ha perdido su destino existente. Una prueba de nulidad gestiona el estado vacío opcional de un puntero inicializado; no demuestra que un destino no nulo siga existiendo.
Un acceso no válido tiene comportamiento indefinido. C no garantiza ni un valor concreto ni un cierre inesperado, incluso cuando una ejecución parece normal.
- 12DesafíoSeguir una dirección copiada
¿Cuáles son los valores finales de
ayb?int a = 6, b = 19; int *p = &a, *q = p; p = &b; *q = *p + 1;---reveal---
apasa a ser 20 ybsigue siendo 19. Redirigirpdejaqapuntando aa. La última línea leebmediantepy escribe la suma enamedianteq. - 13RepasoSeguir el objeto indicado por el lado izquierdo
Lee el lado izquierdo antes de predecir un cambio. Asignar un valor a
pcambia una dirección; asignar un valor a*pmodifica su destino. Mantén los punteros copiados como flechas separadas, comprueba el tiempo de vida del destino y utiliza el tipo apuntado para interpretar cada acceso.
2Hacer que una función modifique datos de la función llamadora
11 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSeguir la dirección dentro de una llamada
- 2ExplicaciónUna dirección copiada sigue llegando a la función llamadora
- 3EjemploGuardar el valor antes de reemplazarlo
- 4EjemploEscribir el segundo valor en la primera posición
- 5EjemploCompletar el intercambio
- 6DesafíoEntregar dos resultados
- 7ExplicaciónColocar const en el nivel deseado
- 8EjemploDevolver una posición válida
- 9DesafíoExplicar el fallo de tiempo de vida
- 10DesafíoElegir una interfaz de búsqueda que permita escribir
- 11RepasoComprobar la escritura y el tiempo de vida
3Recorrer un array sin cruzar sus límites
15 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónEncontrar el inicio, el paso y la parada
- 2ExplicaciónDesplazarse por elementos
- 3EjemploRestar dos posiciones
- 4Idea claveUtilizar un límite que no es un elemento
- 5DesafíoLocalizar el punto medio
- 6ExplicaciónUna expresión de array proporciona un puntero inicial
- 7EjemploAplicar sizeof en el lugar correcto
- 8DesafíoPasar un intervalo más pequeño
- 9EjemploLeer y luego avanzar, o avanzar y luego leer
- 10ExplicaciónEncontrar el final de una cadena
- 11EjemploCopiar un carácter cada vez
- 12ConsejoReservar la posición del terminador
- 13DesafíoCorregir la última visita del bucle
- 14DesafíoElegir una preparación segura para invertir
- 15RepasoIdentificar cada posición visitada
4Modificar un puntero y leer un array de punteros
12 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónHacer que el propio puntero sea un destino
- 2EjemploDibujar dos flechas
- 3ExplicaciónElegir qué nivel cambia
- 4EjemploPasar por dirección la posición de la función llamadora
- 5DesafíoPredecir un cambio solo en el puntero
- 6ConexiónLeer la posición de parada de una conversión
- 7ExplicaciónUn array puede contener posiciones de puntero
- 8EjemploAvanzar por las direcciones
- 9ConsejoComprobar el número de argumentos antes de acceder a cadenas
- 10DesafíoInterpretar el tipo de una fila completa
- 11DesafíoAdaptar una función al almacenamiento proporcionado
- 12RepasoNombrar el objeto de cada nivel
5Elegir una operación y ordenar mediante una función de devolución de llamada
15 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSeparar una tarea general de la operación elegida
- 2ExplicaciónInterpretar el tipo de la función a la que se llama
- 3EjemploSeleccionar una función distinta
- 4ConsejoDar nombre a un tipo de puntero para una tabla
- 5DesafíoInterpretar una declaración relacionada
- 6ExplicaciónProporcionar una acción como función de devolución de llamada
- 7EjemploProporcionar los cuatro argumentos de ordenación
- 8ExplicaciónRecuperar el tipo de elemento entero
- 9EjemploDevolver el signo del orden
- 10DesafíoEvitar un atajo que puede desbordarse
- 11ConexiónOrdenar punteros a cadenas, no filas de caracteres
- 12DesafíoElegir el tamaño correcto para ordenar
- 13Idea claveLa igualdad no garantiza estabilidad
- 14DesafíoSeguir una función de comparación seleccionada
- 15RepasoHacer que coincidan la llamada, la disposición y el orden
Ejercicios de Punteros y direcciones de memoria en C
Set 1: Seguir direcciones y llamadas a funciones10 Preguntas de Práctica
Pregunta 1 de 10
int ocupa 4 bytes. Un array int levels[7] comienza en la dirección ilustrativa 0x2400. ¿En qué dirección comienza levels[4]?Mostrar respuestaOcultar respuesta
0x10, y la dirección es 0x2410. Sumar solo 4 a la dirección trataría el índice como un número de bytes y llegaría al segundo elemento.Pregunta 2 de 10
int items = 16;, ¿qué declaración crea un puntero a items?Mostrar respuestaOcultar respuesta
&items obtiene la dirección del entero e int *p declara el tipo de puntero correspondiente. Inicializar el puntero con items proporcionaría el entero almacenado 16 en lugar de la dirección de un objeto entero. Obtener una dirección deja el valor de items sin cambios.Pregunta 3 de 10
¿Qué valor contiene n después de este código?
int n = 14;
int *p = &n;
*p = 23;
Mostrar respuestaOcultar respuesta
p contiene la dirección de n, por lo que *p designa ese mismo entero. Asignar 23 mediante *p reemplaza el valor de n. Mantener 14 supondría confundir una escritura mediante el puntero con un cambio de la dirección almacenada en el propio puntero.Pregunta 4 de 10
¿Cuál es el valor de score después de que retorne clear(score)?
void clear(int value) { value = 0; }
/* In the caller: */
int score = 27;
clear(score);
Mostrar respuestaOcultar respuesta
value modifica ese parámetro, por lo que score de la función llamadora sigue siendo 27. Para obtener cero haría falta una función que recibiera &score mediante un puntero a entero y escribiera en el objeto apuntado.Pregunta 5 de 10
En un sistema con punteros a objetos de 8 bytes, ¿qué imprime esto?
char label[12] = "oak";
char *p = label;
printf("%zu %zu
", sizeof label, sizeof p);
Mostrar respuestaOcultar respuesta
label es un array de doce caracteres, por lo que sizeof label es 12 aunque el texto almacenado solo tenga tres letras. p es un objeto de tipo puntero independiente y ocupa 8 bytes en este sistema. Contar el terminador da la parte inicial ocupada por la cadena, no el tamaño declarado del array.Pregunta 6 de 10
¿Cuáles son los valores de left y right después de la llamada?
void exchange(int *a, int *b) {
int *t = a;
a = b;
b = t;
}
/* In the caller: */
int left = 11, right = 29;
exchange(&left, &right);
Mostrar respuestaOcultar respuesta
*a ni a *b. No se escribe en ningún objeto entero, por lo que left sigue siendo 11 y right sigue siendo 29. Esperar que los valores se intercambien confunde cambiar los valores de las direcciones locales con copiar los enteros a los que se llega mediante esas direcciones.Pregunta 7 de 10
int read_count(int *out);. Devuelve 1 si tiene éxito y escribe el recuento solo en ese caso. ¿Qué código de la función llamadora imprime de forma segura un recuento recién leído?Mostrar respuestaOcultar respuesta
n sin inicializar si la función falla sin escribir. Un puntero sin inicializar no proporciona un objeto de salida válido que la función pueda rellenar.Pregunta 8 de 10
int x = 6, y = 17; const int *p = &x;, ¿qué asignación está permitida?Mostrar respuestaOcultar respuesta
p es un puntero a datos enteros constantes, por lo que puede redirigirse hacia y. La asignación mediante *p se rechaza porque esa vía de acceso es de solo lectura. La restricción afecta al acceso mediante este puntero; el propio x se definió sin const y aún puede modificarse por su nombre.Pregunta 9 de 10
¿Cuáles son los valores finales de a y b?
int a = 4, b = 13;
int *p = &a, *q = p;
*p += 3;
p = &b;
*q = *p - 2;
*p = *q + 5;
Mostrar respuestaOcultar respuesta
a sea igual a 7, y redirigir p deja q apuntando a a. La siguiente asignación establece a en 11 utilizando el valor 13 de b; después, la última escritura establece b en 16. Mantener a en 7 desplaza incorrectamente q cuando solo se redirigió p.Pregunta 10 de 10
¿Qué par imprime la última línea?
int *next_value(void) {
static int n = 20;
n += 3;
return &n;
}
/* In the caller: */
int *first = next_value();
int saved = *first;
int *second = next_value();
printf("%d %d
", saved, *first + *second);
Mostrar respuestaOcultar respuesta
saved. La segunda llamada cambia el mismo entero estático a 26 y ambos punteros devueltos llegan a él, por lo que sus valores suman 52. El resultado 49 trata el primer puntero como si conservara una instantánea, pero solo saved contiene un entero copiado independiente.Set 2: Razonar sobre intervalos de arrays y cadenas10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
puts(message + 5) para const char *message = "blue sky";?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
double values[8];, ¿cuánto es &values[6] - &values[2]?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Cuál es el valor de *(p - 2) después de estas sentencias?
int bins[] = {6, 12, 18, 24, 30, 36, 42};
int *p = bins + 1;
p += 4;
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
¿Qué devuelve total(data + 1, 3)?
int total(const int *p, size_t n) {
int s = 0;
for (size_t i = 0; i < n; i++) s += p[i];
return s;
}
/* In the caller: */
int data[] = {9, 4, 7, 2, 8};
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
En un sistema con punteros de 8 bytes y valores int de 4 bytes, ¿qué devuelve count(values)?
size_t count(int a[9]) {
return sizeof a / sizeof a[0];
}
/* In the caller: */
int values[9] = {0};
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
¿Cuáles son los valores de taken y a[0] después?
int a[] = {8, 15, 22};
int *p = a;
int taken = *p++;
(*p)++;
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Después de este código, ¿cuál es el mayor entero no negativo k para el que puede formarse p + k dentro del rango permitido para el puntero?
int a[8];
int *p = a + 1, *end = a + 8;
p += (end - p) / 2;
end -= 2;
p += end - p;
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Una cadena modificable terminada en nulo se invierte con esta preparación. ¿Qué entrada pone de manifiesto un cálculo de puntero no válido antes de que comience el bucle?
size_t n = strlen(s);
char *left = s;
char *right = s + n - 1;
while (left < right) {
char t = *left; *left++ = *right; *right-- = t;
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
int a[6] comienza en la dirección ilustrativa 0x3000 y cada entero ocupa 4 bytes. ¿Qué direcciones representan a + 1 y &a + 1?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
¿Cuáles son los valores de count y p - text después de este bucle?
char text[] = "abaca";
char *p = text;
int count = 0;
while (*p != '\0') {
if (*p++ == 'a') count++;
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 3: Seguir niveles de punteros y disposiciones de almacenamiento10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
¿Cuánto vale *p después?
int a = 18, b = 25;
int *p = &a;
int **pp = &p;
*pp = &b;
**pp -= 4;
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
int n = 31; int *p = &n; int **pp = &p;, ¿cuál es el tipo de *pp?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Con punteros a objetos de 8 bytes, ¿cuánto es sizeof words?
const char *words[] = {"red", "violet", "gold", "teal"};
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
char *end; long n = strtol("57kg", &end, 10);, ¿a qué apunta end después de la conversión?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
./paint amber 6 en el entorno alojado habitual. ¿Cuáles son los valores de argc y argv[2][0]?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
int board[2][5] de una función llamadora después de su conversión habitual de array?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
¿Cuáles son los valores de a, b y *p después de la llamada?
void shift(int **slot, int *next) {
**slot += 2;
*slot = next;
**slot += 3;
}
/* In the caller: */
int a = 5, b = 12;
int *p = &a;
shift(&p, &b);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
map[2][3] respecto de map[0][0] y cuál es el tipo de map + 1, para int map[4][6] con enteros de 4 bytes?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
¿Cuál es el valor de word y cuál es el índice de p después?
char word[] = "abcd";
char *p = word;
char saved = *p++;
*p = saved;
p += 2;
*p = 'z';
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 4: Elegir operaciones y construir funciones de comparación para ordenar10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
int (*filter)(int);?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
void visit(int *a, size_t n, void (*action)(int *)); y está disponible void mark(int *x);. ¿Cómo proporciona una función llamadora mark para un array data de cinco elementos?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Qué valor se asigna a result?
int difference(int a, int b) { return a - b; }
int product(int a, int b) { return a * b; }
int (*op)(int, int) = difference;
op = product;
int result = (*op)(7, 4);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
int readings[8];, ¿qué llamada ordena todos los elementos utilizando una función de comparación compare compatible?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
¿Cuáles son los valores de result y calls después de este código?
int calls = 0;
int add_one(int x) { calls++; return x + 1; }
int twice(int x) { calls++; return x * 2; }
/* In a function: */
int (*ops[])(int) = {twice, add_one};
int result = ops[1](ops[0](5));
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
int representables en orden ascendente. ¿Qué expresión de retorno cumple ese requisito después de leer los elementos en x e y?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
const char *labels[], ¿qué expresión obtiene la cadena correspondiente al parámetro de comparación const void *a antes de llamar a strcmp?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
int (*ops[3])(int, int) tiene funciones válidas en las posiciones 0 y 2, y NULL en la posición 1. Para un entero con signo choice, ¿qué condición permite ejecutar ops[choice](8, 2) de forma segura?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
{4, -4, 2, -2} con qsort. ¿Qué resultado está garantizado?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Una lista se ordena por longitud creciente de cadenas mediante este cuerpo de función de devolución de llamada. ¿Cuál es el defecto?
const char *x = *(const char *const *)a;
const char *y = *(const char *const *)b;
size_t nx = strlen(x), ny = strlen(y);
return nx < ny;
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Ajuste de un parámetro de tipo array a puntero
int a[10] es un puntero dentro de la función, por lo que sizeof a mide ese puntero.Alineación
argc
argc es el número no negativo de argumentos que se pasa a main en un entorno alojado; cuenta las cadenas proporcionadas mediante argv, incluido el nombre del programa cuando está presente.argv[2], comprueba que argc > 2 para que la cadena de argumento solicitada exista realmente.argv
argv es la lista de punteros a caracteres que se pasa a main en un entorno alojado, con un puntero nulo en el índice argc y cadenas de argumentos del usuario que comienzan en el índice 1../sum 8 13, argv[2] llega a "13"; el argumento es texto antes de la conversión numérica.Aritmética de punteros
sizeof.Array bidimensional
int grid[2][5] se convierte en int (*)[5]; pasarlo como int ** describiría una disposición distinta.Array de punteros
Array irregular
"red" y "violet" sin reservar filas de caracteres del mismo tamaño para ambas palabras.B
Byte
sizeof; un char de C siempre ocupa un byte, aunque los tamaños de otros tipos dependen del sistema.C
Carácter nulo
'\0', que termina una cadena de C dentro de su almacenamiento accesible de caracteres.Comportamiento indefinido
Conversión de array a puntero
int *p = values; inicializa un puntero a values[0]; el array original sigue conteniendo todos sus elementos.D
Declarador de puntero
* con un nombre declarado individual para indicar que el objeto nombrado tiene un tipo de puntero.int *left, right;, solo left es un puntero; declarar right como puntero necesita otro asterisco.Desbordamiento de búfer
Desbordamiento de enteros con signo
x - y puede desbordarse aunque cada entrada por separado quepa en un int.Desreferenciación
* unario a un puntero para designar el objeto apuntado, lo que permite una lectura o escritura adecuada mediante esa ruta.int *p = &score;, asignar *p = 28; modifica el mismo entero que después se lee mediante score.Diferencia entre punteros
&a[5] - &a[2] da 3 elementos, independientemente de cuántos bytes ocupe un elemento en ese sistema.Dirección de memoria
0x2000, sus bytes restantes siguen hasta 0x2003.Duración automática del almacenamiento
Duración del almacenamiento
Duración estática del almacenamiento
F
Fallo de segmentación
Firma de función
Función de comparación
(x > y) - (x < y) ordena enteros de forma ascendente sin restar los propios valores.Función de devolución de llamada
I
Incremento posfijo
*p++.int x = *p++; lee el elemento de la posición anterior y deja p apuntando a la posición siguiente.L
Límites de un array
a - 1 para un puntero al inicio de un array no es válido, aunque el programa nunca lea mediante él.Literal de cadena
const char *p = "oak"; expone el literal mediante una vía de solo lectura, mientras que char a[] = "oak"; crea una copia modificable.M
Marco de pila
N
Nivel de indirección
int **pp, la primera desreferenciación da un int * y la segunda llega a un objeto entero.O
Operador de indexación
a[i] como *(a + i), utilizando el tipo de elemento del puntero para ajustar la posición.p apunta al elemento original 2, entonces p[1] accede al elemento original 3 del mismo array.Operador de obtención de dirección
& obtiene la dirección de un objeto cuya dirección puede obtenerse o de una función, y produce un tipo de puntero correspondiente.&values[2] proporciona la dirección del tercer elemento sin modificar su contenido.Orden de los bytes
0x12345678 de cuatro bytes tiene los bytes 78 56 34 12 en orden de direcciones menores a mayores en un sistema con el byte menos significativo primero.Orden por filas
int a[3][4], sumar 1 al a convertido avanza una fila completa de cuatro enteros, no un entero.Ordenación estable
qsort no ofrece esa garantía.P
Parámetro de salida
Parámetro de tipo puntero
&score mediante int *out y escribe cero en el entero de la función llamadora utilizando *out.Paso por referencia
&x y &y modifica variables de la función llamadora escribiendo mediante los dos parámetros locales de tipo puntero.Paso por valor
int deja sin cambios el entero de la función llamadora; escribir mediante una dirección copiada puede modificarlo.Precedencia de operadores
++ posfijo tiene mayor precedencia que el * unario, por lo que *p++ se agrupa como *(p++), no como (*p)++.ptrdiff_t
ptrdiff_t es el tipo entero con signo utilizado para la resta de punteros; una resta definida debe tener un resultado representable en ese tipo.<stddef.h> para ptrdiff_t y utiliza %td para imprimir una diferencia entre posiciones válidas del mismo array.Puntero
int *p = &count;, la variable de tipo puntero almacena una dirección mientras que el entero conserva su propio valor.Puntero a datos constantes
const int *p = &x; permite p = &y;, mientras que una asignación como *p = 3; se rechaza.Puntero a función
int (*op)(int, int) = add; almacena una función de suma a la que después se puede llamar como op(4, 7).Puntero a la posición inmediatamente posterior al último elemento
int a[5], un recorrido puede detenerse cuando su puntero actual sea igual a a + 5, antes de acceder a otro elemento.Puntero a puntero
int **pp = &p;, asignar *pp = &y; redirige p, mientras que asignar **pp = 8; modifica su destino.Puntero a un array
int a[4], &a tiene tipo int (*)[4]; su paso aritmético es distinto del de &a[0].Puntero al final de la conversión
"73ms" con strtol puede devolver 73 y hacer que el puntero al final de la función llamadora apunte a la m.Puntero colgante
Puntero constante
int *const p = &x;, *p = 3; está permitido, pero p = &y; no puede redirigir el puntero.Puntero genérico a objeto
void *, o una forma con calificador, para contener una dirección de objeto que debe recuperar un tipo adecuado antes del acceso.const void * en const int * antes de leer el elemento del array cuya dirección recibió.Puntero nulo
NULL cuando no hay coincidencia, por lo que la función llamadora comprueba el resultado antes de desreferenciarlo.Q
qsort
qsort es la función de biblioteca estándar que reorganiza un array utilizando su número de elementos, el tamaño de cada elemento y una función de comparación proporcionada.sizeof names[0] para el tamaño de la posición, no el número de caracteres de una cadena.S
Segmento de un array
data + 2 con un número de elementos de 3 selecciona los índices originales del 2 al 4 sin copiarlos.sizeof
sizeof indica el tamaño de almacenamiento de un tipo u objeto en bytes y devuelve un valor del tipo entero sin signo size_t.char code[10] = "go";, sizeof code indica 10 aunque el texto solo tenga dos caracteres.strtol
strtol es la función de biblioteca estándar que convierte texto numérico inicial a long y puede indicar su posición de parada mediante char **.&end permite que strtol reemplace el puntero a carácter de la función llamadora por la dirección del primer carácter no utilizado.T
Tabla de despacho
typedef
typedef introduce un nombre para un tipo existente, lo que facilita reutilizar un tipo complejo de puntero a función en declaraciones.typedef int (*operation)(int);, operation chosen; declara un puntero a función, no el cuerpo de una función.U
Uso de alias
int *q = p;, ambos punteros llegan al mismo entero hasta que una asignación posterior redirige uno de ellos.V
Valor indeterminado de un puntero
int *p; por sí sola no permite que el programa compruebe, siga o escriba mediante ese puntero de forma segura.Sobre esta unidad
Crea tus propios Materiales de Estudio
Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.