Notas de aprendizaje en lenguaje C — 26.8.28
1. C99, x86 y x64
1.1 ¿Qué es C99? ¿Cuál es su utilidad para el lenguaje C?
C99 es una normativa del lenguaje C que se estableció en el año 1999.
Por ejemplo: Antes de C99, era obligatorio declarar las variables antes de usarlas. Así:
int a;
a = 10;
1.2 ¿Qué significan x86 y x64?
x86 y x64 son parámetros relacionados con la arquitectura de CPU.
Es importante entender este concepto:
1 Byte = 8 bits
① x86
Este nombre proviene de una serie temprana de procesadores de Intel, seindo un nombre de familia de arquitecturas.
Se refiere específicamente a una extensión de 32 bits.
② x64
Es una extensión de 64 bits basada en la arquitectura x86.
(También conocida como x86-64).
③ ¿Y cómo se relaciona esto con C?
En cuanto a punteros, diferentes arquitecturas de CPU producen resultados distintos.
Por ejemplo:
int a = 6;
int* p = &a;
printf("%zu", sizeof(p));
Al ejecutar en etnornos de compilación distintos, los resultados varían:
- En x86, el resultado es
4. - En x64, el resultado es
8.
Motivo:
El tipo
intocupa 4 bytes, es decir, 32 bits. En x86, el tamaño del puntero es de 4 bytes.
Análogamente:
Aunque un valor de 32 bits puede contenerlo en x64, el sistema provee 64 bits, por lo tanto el puntero ocupa 8 bytes.
Nota:
La cantidad de bits de la CPU no altera el tamaño de los tipos básicos de datos como int, que generalmente ocupan 4 bytes.
Sin embargo, el tamaño de los punteros depende del entorno de ejecución.
- Tipos de datos en C, entrada/salida y operadores
2.1 Usando (a % p + p) % p para asegurar que el resultado del módulo sea positivo cuando p > 0
En C, el cálculo del módulo se realiza mediante:
a % b = a - (a / b) * b
Ejemplo:
3 % 5 = 3 = 3 - (3 / 5) * 5
Este proceso asegura que el resultado de a % b esté dentro del rango [0, b-1].
2.2 Operadores
Consultar tabla 1.
2.3 Operadores bit a bit
Los operadores bit a bit operan sobre enteros representados en binario.
- Comportamiento indefinido (UB - Undefined Behavior)
3.1 ¿Qué es UB?
No es un error en sí mismo, el compilador permite compilar pero no garantiza el comportamiento correcto.
Puede interpretarse como:
El código se ejecuta, pero el resultado puede ser incorrecto.
A continuación se presentan ejemplos de comportamiento indefinido:
Nota: Debido a la naturaleza impredecible de UB, puede provocar fallos o errores fatales, impidiendo que el resto del código se ejecute.
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{
/*
* ============================================================
* 1. División por cero
* ============================================================
*/
/*
int a = 10;
int b = 0;
int c = a / b; // UB: división entera por cero
printf("%d\n", c);
*/
/*
* ============================================================
* 2. Desbordamiento de array
* ============================================================
*/
/*
int arr[5] = {1, 2, 3, 4, 5};
printf("%d\n", arr[10]); // UB: acceso fuera de límites del arreglo
*/
/*
* ============================================================
* 3. Uso de variable no inicializada
* ============================================================
*/
/*
int a;
printf("%d\n", a); // UB: lectura de variable automática no inicializada
*/
/*
* ============================================================
* 4. Desreferencia de puntero nulo
* ============================================================
*/
/*
int *p = NULL;
printf("%d\n", *p); // UB: desreferenciar puntero nulo
*/
/*
* ============================================================
* 5. Uso de memoria liberada
* ============================================================
*/
/*
int *p = malloc(sizeof(int));
*p = 100;
free(p);
printf("%d\n", *p); // UB: uso tras liberación (Use-After-Free)
*/
/*
* ============================================================
* 6. Retornar dirección de variable local después de su uso
*
* Nota:
* No se puede hacer directamente en main con return &a,
* ya que main no debería devolver esa dirección.
*
* Se puede demostrar mediante una función auxiliar.
* ============================================================
*/
/*
int *get_address(void)
{
int a = 10;
return &a; // Error: a es local
}
int *p = get_address();
printf("%d\n", *p); // UB: el ciclo de vida de a ha terminado
*/
/*
* ============================================================
* 7. Acceso inválido a memoria mediante puntero
* ============================================================
*/
/*
int a = 10;
int *p = &a;
printf("%d\n", *(p + 100)); // UB: acceso a memoria no perteneciente a a
*/
/*
* ============================================================
* 8. Modificación de literales de cadena
* ============================================================
*/
/*
char *p = "Hello";
p[0] = 'h'; // UB: intento de modificar literal de cadena
*/
/*
* ============================================================
* 9. Desbordamiento de enteros con signo
* ============================================================
*/
/*
int a = INT_MAX;
a = a + 1; // UB: desbordamiento de entero con signo
printf("%d\n", a);
*/
/*
* ============================================================
* 10. Modificar múltiples veces la misma variable en una expresión
* ============================================================
*/
/*
int i = 1;
int ret = (++i) + (++i) + (++i); // UB
printf("%d %d\n", i, ret);
*/
/*
* Otro ejemplo común:
int i = 1;
int ret = i++ + i++; // UB
printf("%d\n", ret);
*/
/*
* ============================================================
* 11. Tipo de scanf no coincidente
* ============================================================
*/
/*
int a;
scanf("%c", &a); // UB: %c requiere char*, aquí se pasa int*
*/
/*
* Forma correcta:
char c;
scanf("%c", &c);
*/
/*
* ============================================================
* 12. Incompatibilidad entre especificador de formato y tipo en printf
* ============================================================
*/
/*
int a = 10;
printf("%f\n", a); // UB: %f espera double en printf
*/
/*
* Correcto:
double a = 10.0;
printf("%f\n", a);
*/
/*
* ============================================================
* 13. Desplazamiento inválido
* ============================================================
*/
/*
int a = 1;
int b = a << 32; // Suponiendo int de 32 bits
// UB: cantidad de desplazamiento inválida
*/
/*
* Caso típico adicional:
int a = -1;
int b = a << 1; // UB: desplazamiento izquierdo de número negativo
*/
/*
* ============================================================
* 14. Acceso a dirección de memoria inválida
* ============================================================
*/
/*
int *p = (int *)0x12345678;
*p = 100; // UB: esa dirección puede no ser válida para escritura
*/
/*
* ============================================================
* 15. Desreferencia de puntero erróneo
* ============================================================
*/
/*
int *p; // p no está inicializado, contiene una dirección aleatoria
*p = 10; // UB: acceso a memoria mediante puntero erróneo
*/
return 0;
}
3.2 Ejemplos
#include <stdio.h>
int main()
{
// Modificadores de salida
printf("%+d\n", 123); // +123: muestra el signo del número
printf("%5d\n", 123); // longitud mínima de salida de 5 caracteres, alineado a la derecha
printf("%-5d\n", 123); // alineado a la izquierda, mínimo de 5 caracteres
printf("%.2lf\n", 3.145); // muestra dos decimales, redondeando según regla habitual
printf("%.5s\n", "Hello World"); // limita la longitud de caracteres a mostrar
printf("%*d\n", 5, 123); // longitud dinámica: imprime 123 con espacios al inicio hasta 5 caracteres
// Modificadores de entrada
int a = 0, b = 0;
scanf("%d%*c%d", &a, &b); // %*c ignora el carácter leído
scanf(" %c", &a); // el espacio salta saltos de línea, espacios y tabulaciones antes de leer
// Múltiples modificaciones en una expresión (UB)
int i = 1, ret = 0;
ret = (++i) + (++i) + (++i);
printf("%d %d\n", i, ret);
// sizeof es un operador
printf("%zu %zu\n", sizeof a, sizeof(long));
// Salida en octal y hexadecimal
printf("%d %d\n", 0xf, 017);
return 0;
}