Notas de aprendizaje en lenguaje C — 26.8.28

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 int ocupa 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.

  1. 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.

  1. 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;
}


Etiquetas: C99 x86 x64 tipos de datos operadores

Publicado el 10-24 18:19