Skip to article frontmatterSkip to article content

Biblioteca Estándar de C

Referencia completa de las bibliotecas estándar de C organizadas por header.

Introducción a la Biblioteca Estándar de C

La biblioteca estándar de C (Standard Library) es un conjunto de funciones, macros y tipos definidos que proporcionan servicios esenciales para el desarrollo de programas en C. Estos componentes están estandarizados por ISO/IEC 9899 y están disponibles en cualquier implementación conforme al estándar.

La biblioteca está organizada en archivos de cabecera (headers), cada uno dedicado a un dominio específico de funcionalidad. Incluir un header mediante la directiva #include da acceso a las declaraciones de funciones, constantes y tipos definidos en ese módulo.

Principios de la Biblioteca Estándar


<assert.h> - Diagnóstico y Aserciones

El header <assert.h> proporciona la macro assert() para verificar suposiciones durante el desarrollo. Las aserciones ayudan a detectar errores de lógica de forma temprana.

Macro assert

void assert(scalar expression);

Comportamiento:

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <assert.h>
#include <stdio.h>

int dividir(int a, int b)
{
    assert(b != 0);  // Precondición: divisor no puede ser cero
    return a / b;
}

int main(void)
{
    int resultado = dividir(10, 2);
    printf("10 / 2 = %d\n", resultado);
    
    // La siguiente línea abortará el programa en modo debug
    // resultado = dividir(10, 0);
    
    return 0;
}

Mensaje de error típico:

Assertion failed: (b != 0), function dividir, file programa.c, line 5.
Abort trap: 6

Compilación con aserciones desactivadas:

gcc -DNDEBUG programa.c -o programa

<ctype.h> - Clasificación y Transformación de Caracteres

El header <ctype.h> proporciona funciones para clasificar y transformar caracteres individuales. Todas estas funciones aceptan un int (representando un unsigned char o EOF) y devuelven un int.

Funciones de Clasificación

Retornan un valor no-cero (verdadero) si el carácter cumple la condición, 0 en caso contrario.

isalnum - Alfanumérico

int isalnum(int c);

Verifica si c es una letra (a-z, A-Z) o un dígito (0-9).

La función isalnum() es extremadamente útil para validar entradas que deben contener solo caracteres alfanuméricos, como nombres de usuario, identificadores o códigos. Su implementación considera la configuración regional actual del sistema (locale), lo que significa que puede reconocer caracteres acentuados como válidos en ciertos idiomas. Esta función es fundamental en el procesamiento de texto y validación de datos, especialmente cuando se necesita filtrar caracteres especiales o espacios en blanco de una cadena de entrada.

isalnum('a');  // → no-cero (verdadero)
isalnum('5');  // → no-cero (verdadero)
isalnum('!');  // → 0 (falso)

isalpha - Alfabético

int isalpha(int c);

Verifica si c es una letra del alfabeto.

Esta función es esencial para el análisis léxico y el procesamiento de texto. isalpha() identifica únicamente las letras del alfabeto, excluyendo dígitos, espacios y caracteres especiales. Es particularmente útil en parsers, analizadores de código, y funciones que necesitan distinguir entre contenido textual y otros tipos de caracteres. Al igual que otras funciones de <ctype.h>, considera la configuración regional, permitiendo el reconocimiento de caracteres específicos del idioma como ñ, ç, ü, etc., dependiendo del locale configurado en el sistema.

isalpha('A');  // → verdadero
isalpha('z');  // → verdadero
isalpha('3');  // → falso

isdigit - Dígito Decimal

int isdigit(int c);

Verifica si c es un dígito decimal (0-9).

isdigit() es una función fundamental para la validación numérica y el parsing de números en cadenas de texto. A diferencia de isalnum(), esta función se limita estrictamente a los dígitos decimales del 0 al 9, sin considerar caracteres alfabéticos. Es especialmente valiosa en funciones que convierten cadenas a números, validadores de entrada numérica, y en algoritmos que procesan datos mixtos donde se necesita separar contenido numérico del textual. Su comportamiento es independiente del locale, garantizando consistencia en diferentes configuraciones regionales.

isdigit('7');  // → verdadero
isdigit('a');  // → falso

isxdigit - Dígito Hexadecimal

int isxdigit(int c);

Verifica si c es un dígito hexadecimal (0-9, a-f, A-F).

Esta función es indispensable en aplicaciones que manejan representaciones hexadecimales de datos, como parsers de colores HTML, convertidores de formato numérico, y herramientas que procesan direcciones de memoria o valores hash. isxdigit() reconoce tanto dígitos decimales como las letras a-f (y A-F), que representan los valores 10-15 en hexadecimal. Es crucial en sistemas que requieren conversión entre bases numéricas y en el procesamiento de datos binarios representados como texto hexadecimal.

isxdigit('F');  // → verdadero
isxdigit('g');  // → falso

islower y isupper - Mayúsculas y Minúsculas

int islower(int c);
int isupper(int c);

Verifican si c es una letra minúscula o mayúscula, respectivamente.

Estas funciones son fundamentales para el procesamiento de texto que requiere análisis de capitalización. islower() identifica letras en minúscula, mientras que isupper() identifica letras en mayúscula. Son especialmente útiles en algoritmos de normalización de texto, validadores de formato de identificadores (como CamelCase o snake_case), y en sistemas que implementan reglas específicas de capitalización. Ambas funciones consideran la configuración regional, por lo que pueden manejar apropiadamente caracteres acentuados y específicos del idioma, como distinguir entre ‘á’ y ‘Á’ según el locale configurado.

islower('a');  // → verdadero
isupper('A');  // → verdadero
islower('A');  // → falso

isspace - Espacio en Blanco

int isspace(int c);

Verifica si c es un carácter de espacio en blanco: espacio (' '), tabulador ('\t'), nueva línea ('\n'), retorno de carro ('\r'), avance de página ('\f'), tabulador vertical ('\v').

isspace() es una función crucial para el parsing y tokenización de texto, ya que permite identificar todos los tipos de caracteres de espacio en blanco que separan palabras y elementos en un documento. Esta función es más completa que una simple comparación con espacio, ya que reconoce múltiples tipos de separadores invisibles que pueden aparecer en texto procesado desde diferentes fuentes y sistemas operativos. Es especialmente valiosa en analizadores léxicos, funciones que eliminan espacios en blanco (trim), y en algoritmos que procesan entrada de usuario donde los espacios deben ser manejados apropiadamente.

isspace(' ');   // → verdadero
isspace('\n');  // → verdadero
isspace('a');   // → falso

isprint y isgraph - Imprimibles

int isprint(int c);
int isgraph(int c);

Estas funciones son esenciales para validar y filtrar caracteres en aplicaciones que manejan entrada de usuario o procesan texto de fuentes externas. isprint() identifica caracteres que pueden ser mostrados visualmente, incluyendo el espacio, lo que la convierte en un filtro efectivo contra caracteres de control que podrían causar problemas de visualización o seguridad. isgraph() es más restrictiva, excluyendo el espacio, y es útil cuando se necesita identificar únicamente caracteres “gráficos” verdaderos. Ambas son fundamentales en sistemas que validan contenido textual, escapan caracteres especiales, o preparan texto para visualización segura en interfaces de usuario.

isprint('A');   // → verdadero
isprint(' ');   // → verdadero
isgraph(' ');   // → falso

iscntrl y ispunct - Control y Puntuación

int iscntrl(int c);
int ispunct(int c);

iscntrl() identifica caracteres de control, que son caracteres no imprimibles utilizados para control de formato o comunicación (como ‘\n’, ‘\t’, ‘\0’, etc.). Esta función es crucial en aplicaciones que procesan texto crudo o datos de comunicación, donde los caracteres de control deben ser manejados de forma especial o filtrados. ispunct() reconoce caracteres de puntuación como ‘.’, ‘,’, ‘!’, ‘?’, ‘;’, etc., siendo esencial en analizadores de texto, procesadores de lenguaje natural, y sistemas que necesitan separar puntuación del contenido alfabético. Ambas funciones son fundamentales para la limpieza y categorización de datos textuales.

Funciones de Transformación

tolower y toupper - Conversión de Caso

int tolower(int c);
int toupper(int c);

Convierten un carácter a minúscula o mayúscula. Si el carácter no tiene equivalente (ej. un dígito), lo devuelven sin cambios.

Estas funciones son pilares de la manipulación de texto en C, permitiendo normalización de casos para comparaciones insensibles a mayúsculas, generación de identificadores consistentes, y formateo de salida. toupper() convierte letras minúsculas a mayúsculas, mientras que tolower() hace lo contrario. Ambas funciones son “seguras” en el sentido de que no alteran caracteres que no son letras, devolviendo el carácter original sin modificación. Son especialmente útiles en sistemas de búsqueda, validación de entrada de usuario, y algoritmos que requieren comparación de texto independiente del caso. Su comportamiento depende del locale configurado, permitiendo manejo apropiado de caracteres internacionales.

toupper('a');  // → 'A'
tolower('Z');  // → 'z'
tolower('3');  // → '3' (sin cambio)

Ejemplo completo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char cadena[] = "Hola123!";
    
    printf("Análisis de caracteres:\n");
    for (size_t i = 0; cadena[i] != '\0'; i++) {
        char c = cadena[i];
        printf("'%c': ", c);
        
        if (isalpha(c)) printf("alfa ");
        if (isdigit(c)) printf("dígito ");
        if (isspace(c)) printf("espacio ");
        if (ispunct(c)) printf("puntuación ");
        
        printf("\n");
    }
    
    return 0;
}

<errno.h> - Reporte de Errores

El header <errno.h> define la variable global errno y constantes para códigos de error. Muchas funciones de la biblioteca estándar establecen errno cuando fallan.

Variable errno

extern int errno;

Esta variable global almacena el código del último error ocurrido. No se resetea automáticamente; el programador debe establecerla a 0 antes de llamar a funciones que puedan modificarla.

La variable errno es el mecanismo estándar de reporte de errores en C, funcionando como un canal de comunicación entre las funciones de biblioteca y el código de aplicación. Su diseño global permite que cualquier función de biblioteca indique errores sin necesidad de cambiar su interfaz de retorno. Es fundamental entender que errno mantiene su valor hasta que otra función lo modifique, por lo que debe ser explícitamente inicializada a 0 antes de llamar funciones que puedan fallar. Este enfoque permite distinguir entre fallas reales y valores de retorno que podrían ser ambiguos (como cuando malloc() retorna NULL o strtol() retorna 0).

Códigos de error comunes (macro constantes):

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <string.h>

int main(void)
{
    errno = 0;  // Resetear antes de la operación
    
    double resultado = sqrt(-1.0);
    
    if (errno == EDOM) {
        fprintf(stderr, "Error de dominio: %s\n", strerror(errno));
    }
    
    return 0;
}

Función strerror

Aunque está definida en <string.h>, se usa comúnmente con errno:

char *strerror(int errnum);

Retorna un puntero a una cadena que describe el código de error errnum.

fprintf(stderr, "Error: %s\n", strerror(errno));

<float.h> - Límites de Tipos de Punto Flotante

El header <float.h> define constantes que caracterizan la implementación de los tipos de punto flotante (float, double, long double).

Macros Principales

Precisión y Dígitos

Rangos

Epsilon

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <float.h>

int main(void)
{
    printf("Límites de float:\n");
    printf("  Precisión: %d dígitos decimales\n", FLT_DIG);
    printf("  Mínimo: %e\n", FLT_MIN);
    printf("  Máximo: %e\n", FLT_MAX);
    printf("  Epsilon: %e\n", FLT_EPSILON);
    
    printf("\nLímites de double:\n");
    printf("  Precisión: %d dígitos decimales\n", DBL_DIG);
    printf("  Epsilon: %e\n", DBL_EPSILON);
    
    return 0;
}

Salida típica:

Límites de float:
  Precisión: 6 dígitos decimales
  Mínimo: 1.175494e-38
  Máximo: 3.402823e+38
  Epsilon: 1.192093e-07

<limits.h> - Límites de Tipos Enteros

El header <limits.h> define constantes que especifican los límites de los tipos enteros en la implementación actual.

Macros de Límites para Tipos Enteros

Char

Short

Int

Long

Long Long (C99)

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <limits.h>

int main(void)
{
    printf("Límites de tipos enteros:\n");
    printf("CHAR_BIT: %d bits\n", CHAR_BIT);
    printf("INT_MIN: %d\n", INT_MIN);
    printf("INT_MAX: %d\n", INT_MAX);
    printf("UINT_MAX: %u\n", UINT_MAX);
    printf("LONG_MAX: %ld\n", LONG_MAX);
    printf("LLONG_MAX: %lld\n", LLONG_MAX);
    
    return 0;
}

<math.h> - Funciones Matemáticas

El header <math.h> proporciona funciones matemáticas comunes. La mayoría opera sobre double, pero C99 añadió versiones para float (sufijo f) y long double (sufijo l).

Para compilar programas que usan <math.h>, típicamente se debe enlazar con la biblioteca matemática:

gcc programa.c -o programa -lm

Funciones Trigonométricas

double sin(double x);    // Seno (x en radianes)
double cos(double x);    // Coseno
double tan(double x);    // Tangente

double asin(double x);   // Arco seno
double acos(double x);   // Arco coseno
double atan(double x);   // Arco tangente
double atan2(double y, double x);  // Arco tangente de y/x

Las funciones trigonométricas son fundamentales en aplicaciones científicas, gráficos por computadora, simulaciones físicas y procesamiento de señales. sin(), cos() y tan() calculan las funciones trigonométricas básicas, recibiendo ángulos en radianes (no en grados). Sus contrapartes inversas asin(), acos() y atan() calculan el ángulo correspondiente dado el valor de la función trigonométrica. atan2(y, x) es especialmente útil porque calcula el arco tangente de y/x considerando el cuadrante, evitando problemas de división por cero y proporcionando el ángulo correcto en el rango [-π, π]. Esta función es esencial en conversiones de coordenadas cartesianas a polares y en cálculos de orientación en gráficos 2D.

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <math.h>

#define PI 3.14159265358979323846

int main(void)
{
    double angulo_grados = 45.0;
    double angulo_radianes = angulo_grados * PI / 180.0;
    
    printf("seno(45°) = %.4f\n", sin(angulo_radianes));
    printf("coseno(45°) = %.4f\n", cos(angulo_radianes));
    printf("tangente(45°) = %.4f\n", tan(angulo_radianes));
    
    return 0;
}

Funciones Exponenciales y Logarítmicas

double exp(double x);     // e^x
double log(double x);     // Logaritmo natural (base e)
double log10(double x);   // Logaritmo base 10
double pow(double x, double y);  // x^y
double sqrt(double x);    // Raíz cuadrada

Las funciones exponenciales y logarítmicas son esenciales en análisis matemático, algoritmos de crecimiento, y modelado científico. exp(x) calcula e elevado a la potencia x, siendo fundamental en cálculos de crecimiento exponencial, distribuciones estadísticas y transformadas de Fourier. log(x) y log10(x) calculan logaritmos naturales y base 10 respectivamente, útiles en algoritmos de complejidad, escalas logarítmicas y compresión de datos. pow(x, y) es una función de potenciación general que calcula x^y, manejando casos especiales como exponentes fraccionarios y negativos. sqrt(x) calcula la raíz cuadrada, optimizada para ser más rápida que pow(x, 0.5) y esencial en cálculos geométricos, algoritmos de distancia y normalización de vectores.

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <math.h>

int main(void)
{
    printf("e^2 = %.4f\n", exp(2.0));
    printf("log(10) = %.4f\n", log(10.0));
    printf("log10(100) = %.4f\n", log10(100.0));
    printf("2^8 = %.0f\n", pow(2.0, 8.0));
    printf("√16 = %.0f\n", sqrt(16.0));
    
    return 0;
}

Funciones de Redondeo

double ceil(double x);    // Redondea hacia arriba
double floor(double x);   // Redondea hacia abajo
double round(double x);   // Redondea al entero más cercano (C99)
double trunc(double x);   // Trunca la parte decimal (C99)

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <math.h>

int main(void)
{
    double valor = 3.7;
    
    printf("ceil(%.1f) = %.0f\n", valor, ceil(valor));      // 4
    printf("floor(%.1f) = %.0f\n", valor, floor(valor));    // 3
    printf("round(%.1f) = %.0f\n", valor, round(valor));    // 4
    printf("trunc(%.1f) = %.0f\n", valor, trunc(valor));    // 3
    
    return 0;
}

Valor Absoluto y Resto

double fabs(double x);    // Valor absoluto
double fmod(double x, double y);  // Resto de x/y

fabs() calcula el valor absoluto de un número de punto flotante. fmod() calcula el resto de la división de punto flotante x/y.

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <math.h>

int main() {
    printf("fabs(-5.5) = %.1f\n", fabs(-5.5));
    printf("fmod(10.5, 3.0) = %.1f\n", fmod(10.5, 3.0)); // 10.5 = 3 * 3.0 + 1.5
    return 0;
}

“Uso de fabs y fmod.”

Funciones Hiperbólicas

double sinh(double x);    // Seno hiperbólico
double cosh(double x);    // Coseno hiperbólico
double tanh(double x);    // Tangente hiperbólica

Calculan las funciones trigonométricas hiperbólicas, que son análogas a las trigonométricas ordinarias pero definidas usando la hipérbola en lugar del círculo. Son fundamentales en cálculo, física e ingeniería.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <math.h>

int main() {
    double x = 1.0;
    printf("sinh(%.1f) = %f\n", x, sinh(x));
    printf("cosh(%.1f) = %f\n", x, cosh(x));
    printf("tanh(%.1f) = %f\n", x, tanh(x));
    return 0;
}

“Cálculo de funciones hiperbólicas.”

Constantes Útiles

Aunque no están en el estándar, muchas implementaciones definen:

#define M_PI    3.14159265358979323846   // π
#define M_E     2.71828182845904523536   // e

<stdbool.h> - Tipo Booleano (C99)

El header <stdbool.h>, introducido en C99, define el tipo bool y las constantes true y false, facilitando el uso de lógica booleana de forma más expresiva.

Definiciones

#define bool  _Bool
#define true  1
#define false 0

El tipo subyacente es _Bool, que puede almacenar 0 o 1.

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdbool.h>

bool es_par(int n)
{
    return n % 2 == 0;
}

int main(void)
{
    bool activo = true;
    bool deshabilitado = false;
    
    if (activo) {
        printf("El sistema está activo.\n");
    }
    
    if (es_par(10)) {
        printf("10 es par.\n");
    }
    
    return 0;
}

<stddef.h> - Definiciones Estándar

El header <stddef.h> define tipos y macros fundamentales que son utilizados ampliamente en otros headers.

Tipos Definidos

size_t

typedef /* implementation-defined */ size_t;

Tipo entero sin signo que representa el tamaño de un objeto en bytes. Es el tipo de retorno de sizeof y se usa para índices de arreglos y tamaños de bloques de memoria.

size_t longitud = strlen("hola");
char buffer[100];
size_t tamano_buffer = sizeof(buffer);

ptrdiff_t

typedef /* implementation-defined */ ptrdiff_t;

Tipo entero con signo que representa la diferencia entre dos punteros.

int arr[10];
int *p1 = &arr[8];
int *p2 = &arr[3];
ptrdiff_t diferencia = p1 - p2;  // 5

wchar_t

typedef /* implementation-defined */ wchar_t;

Tipo entero para representar caracteres anchos (wide characters), usado en internacionalización.

Macro NULL

#define NULL ((void*)0)

Constante de puntero nulo. Representa un puntero que no apunta a ningún objeto válido.

int *ptr = NULL;
if (ptr == NULL) {
    printf("Puntero nulo\n");
}

Macro offsetof

size_t offsetof(type, member);

Calcula el desplazamiento en bytes de un miembro dentro de una estructura.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stddef.h>

struct Datos {
    char c;
    int i;
    double d;
};

int main(void)
{
    printf("Desplazamiento de 'c': %zu bytes\n", offsetof(struct Datos, c));
    printf("Desplazamiento de 'i': %zu bytes\n", offsetof(struct Datos, i));
    printf("Desplazamiento de 'd': %zu bytes\n", offsetof(struct Datos, d));
    
    return 0;
}

Salida típica (puede variar por padding):

Desplazamiento de 'c': 0 bytes
Desplazamiento de 'i': 4 bytes
Desplazamiento de 'd': 8 bytes

<stdint.h> - Tipos Enteros de Ancho Fijo (C99)

El header <stdint.h>, introducido en C99, resuelve un problema fundamental de la portabilidad en C: el tamaño de los tipos enteros como int o long no está garantizado entre diferentes arquitecturas. En un sistema, un int puede tener 32 bits, y en otro, 64. Esto puede romper el código que depende de un tamaño específico, especialmente en áreas como la programación de sistemas, protocolos de red, formatos de archivo o criptografía.

<stdint.h> soluciona esto proveyendo un conjunto de tipos de datos enteros cuyo tamaño en bits es explícito y garantizado.

Tipos de Ancho Exacto

Estos son los tipos más comunes y útiles del header. Tienen exactamente el número de bits especificado. Su uso es obligatorio si el sistema los soporta y tu código depende de un tamaño exacto.

int8_t, int16_t, int32_t, int64_t       // Enteros con signo
uint8_t, uint16_t, uint32_t, uint64_t   // Enteros sin signo

Ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdint.h>

int main(void)
{
    int8_t   byte = 127;            // -128 a 127
    uint8_t  ubyte = 255;           // 0 a 255
    int16_t  palabra = 32767;       // -32768 a 32767
    uint32_t doble_palabra = 4000000000u;
    int64_t  grande = 9223372036854775807LL;
    
    printf("int8_t: %d\n", byte);
    printf("uint8_t: %u\n", ubyte);
    printf("int16_t: %d\n", palabra);
    printf("uint32_t: %u\n", doble_palabra);
    printf("int64_t: %lld\n", grande);
    
    return 0;
}

Tipos de Ancho Mínimo

Garantizan al menos el número de bits especificado. Son más portables que los de ancho exacto, ya que se garantiza su existencia en todas las plataformas conformes.

int_least8_t, int_least16_t, int_least32_t, int_least64_t
uint_least8_t, uint_least16_t, uint_least32_t, uint_least64_t

Uso: Utilizalos cuando necesites un rango mínimo, pero no te importe si el tipo es más grande, y la portabilidad a sistemas exóticos es una prioridad.

Tipos Más Rápidos

Son los tipos enteros que tienen al menos el ancho especificado y son los más rápidos de procesar en la arquitectura de destino.

int_fast8_t, int_fast16_t, int_fast32_t, int_fast64_t
uint_fast8_t, uint_fast16_t, uint_fast32_t, uint_fast64_t

Uso: Ideales para contadores de bucles o cálculos donde el rendimiento es crítico y solo se necesita un rango mínimo.

Tipos de Máximo Ancho

Representan los tipos enteros más grandes que la implementación puede manejar.

intmax_t    // Tipo entero con signo de mayor ancho
uintmax_t   // Tipo entero sin signo de mayor ancho

Tipos para Punteros

Tipos enteros garantizados para poder almacenar un puntero a void sin pérdida de información.

intptr_t    // Entero con signo capaz de contener un puntero
uintptr_t   // Entero sin signo capaz de contener un puntero

Uso: Útiles para aritmética de punteros de bajo nivel o para conversiones (casts) entre punteros y enteros.

Macros de Límites y Constantes

Para cada tipo, hay macros que definen sus límites (INT8_MIN, INT8_MAX, UINT8_MAX, etc.) y macros para definir literales de estos tipos (INT8_C(123), UINT64_C(0x123...)).

INT8_MIN, INT8_MAX, UINT8_MAX
INT16_MIN, INT16_MAX, UINT16_MAX
INT32_MIN, INT32_MAX, UINT32_MAX
INT64_MIN, INT64_MAX, UINT64_MAX
INTMAX_MIN, INTMAX_MAX, UINTMAX_MAX

Ejemplo Práctico: Cabecera de un Paquete de Red

Imaginá que estás definiendo la estructura para un paquete de red. El protocolo exige tamaños de campo exactos para que cualquier sistema pueda leerlo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h> // Para macros de formato como PRId64

// Definimos una cabecera de paquete con tamaños fijos
typedef struct {
    uint16_t source_port;      // Puerto de origen (16 bits)
    uint16_t dest_port;        // Puerto de destino (16 bits)
    uint32_t sequence_number;  // Número de secuencia (32 bits)
    uint8_t  flags;            // Banderas (8 bits)
    uint8_t  protocol_version; // Versión (8 bits)
    int64_t  timestamp_ns;     // Marca de tiempo en nanosegundos (64 bits)
} PacketHeader;

int main(void)
{
    PacketHeader header = {
        .source_port = 49152,
        .dest_port = 80,
        .sequence_number = 1234567890,
        .flags = 0x10, // SYN flag
        .protocol_version = 4,
        .timestamp_ns = 1665789000123456789LL
    };

    // El tamaño es predecible en cualquier arquitectura
    printf("El tamaño de la cabecera del paquete es: %zu bytes\n", sizeof(PacketHeader));

    // Usamos macros de <inttypes.h> para imprimir de forma portable
    printf("Timestamp: %" PRId64 " ns\n", header.timestamp_ns);
    
    return 0;
}

“Uso de stdint.h para portabilidad en un protocolo.”


<stdio.h> - Entrada/Salida Estándar

El header <stdio.h> (de “standard input/output”) es uno de los más importantes y utilizados de C. Proporciona un conjunto de herramientas cohesivo y potente para interactuar con el exterior del programa, ya sea la consola, archivos en disco u otros dispositivos.

El Concepto de Flujo (Stream)

La idea central de <stdio.h> es el flujo (representado por el tipo opaco FILE), una abstracción que unifica la manera en que se manejan las operaciones de entrada y salida. Un flujo es una secuencia de bytes que fluyen desde una fuente (como el teclado o un archivo) hacia el programa, o desde el programa hacia un destino (como la pantalla o un archivo). Gracias a esta abstracción, las mismas funciones (fgetc, fprintf, etc.) pueden usarse para leer de la consola o de un archivo, cambiando únicamente el flujo de origen.

Por defecto, tres flujos estándar están disponibles en todo programa:

Operaciones con Archivos

fopen - Abrir Archivo

FILE *fopen(const char *filename, const char *mode);

Abre un archivo y retorna un puntero a FILE, o NULL si falla. El mode especifica cómo se interactuará con el archivo.

Modos comunes:

Se puede añadir una b al modo (ej. "rb", "wb") para operaciones en modo binario, que evita cualquier tipo de procesamiento o conversión de los bytes (como la conversión de finales de línea \n a \r\n en Windows).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(void)
{
    FILE *archivo = fopen("datos.txt", "r");
    if (archivo == NULL) {
        perror("Error al abrir el archivo");
        return 1;
    }
    
    // Usar el archivo...
    
    fclose(archivo);
    return 0;
}

fclose - Cerrar Archivo

int fclose(FILE *stream);

Cierra el archivo asociado al flujo, asegurando que todos los datos en el buffer se escriban al disco. Retorna 0 si tiene éxito, EOF si hay error.

fflush - Vaciar Buffer

int fflush(FILE *stream);

Fuerza la escritura de datos del buffer de salida al archivo sin tener que cerrarlo. Es útil cuando se necesita garantizar que los datos se guarden en un punto crítico. Retorna 0 si tiene éxito, EOF si hay error.

Funciones de Salida con Formato

printf, fprintf, snprintf

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int snprintf(char *s, size_t n, const char *format, ...);

La familia de funciones printf escribe datos formateados a un flujo (stdout por defecto para printf) o a una cadena de caracteres (snprintf). Retornan el número de caracteres escritos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main(void)
{
    char buffer[50];
    int edad = 25;
    const char* nombre = "Ana";
    
    int escritos = snprintf(buffer, sizeof(buffer), 
                            "%s tiene %d años", nombre, edad);
    
    if (escritos >= 0 && (size_t)escritos < sizeof(buffer)) {
        printf("Cadena construida: %s\n", buffer);
    }
    
    return 0;
}

“Uso de snprintf para construcción segura de cadenas”

Funciones de Entrada con Formato

scanf y fscanf

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);

Leen datos formateados desde stdin o un flujo, interpretando el texto según la cadena de formato. Retornan el número de conversiones exitosas, o EOF si se alcanza el fin del archivo o hay un error.

Entrada y Salida de Caracteres y Líneas

getchar, fgetc, fgets

int getchar(void); // Lee de stdin
int fgetc(FILE *stream); // Lee de un flujo
char *fgets(char *s, int n, FILE *stream); // Lee una línea completa

Estas funciones son más seguras y a menudo más simples para leer datos textuales que scanf.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>

int main(void)
{
    char linea[100];
    
    printf("Ingrese una línea: ");
    if (fgets(linea, sizeof(linea), stdin) != NULL) {
        // fgets incluye el '\n' si hay espacio, lo eliminamos.
        linea[strcspn(linea, "\n")] = '\0';
        
        printf("Leíste: \"%s\"\n", linea);
    }
    
    return 0;
}

“Lectura segura de una línea con fgets”

Posicionamiento en Archivos

fseek, ftell y rewind

int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);

Permiten el acceso aleatorio a los contenidos de un archivo. fseek() mueve el cursor de lectura/escritura, ftell() informa la posición actual y rewind() lo regresa al inicio.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>

int main() {
    FILE *archivo = fopen("ejemplo.txt", "w+");
    if (archivo == NULL) {
        perror("Error al crear archivo");
        return 1;
    }

    fputs("Hola Mundo", archivo);

    // Obtener el tamaño del archivo usando fseek y ftell
    fseek(archivo, 0, SEEK_END);
    long tamano = ftell(archivo);
    printf("El archivo tiene %ld bytes.\n", tamano);

    // Volver al inicio del archivo
    rewind(archivo);

    // Leer el primer carácter
    char c = fgetc(archivo);
    printf("Primer carácter: %c\n", c);

    // Moverse 5 bytes desde el inicio y leer
    fseek(archivo, 5, SEEK_SET);
    char buffer[10];
    fgets(buffer, sizeof(buffer), archivo);
    printf("Desde la posición 5: %s\n", buffer);

    fclose(archivo);
    return 0;
}

“Uso de fseek, ftell y rewind para acceso aleatorio.”

Manejo de Errores en Flujos

feof, ferror y perror

int feof(FILE *stream);
int ferror(FILE *stream);
void perror(const char *s);

feof() verifica fin de archivo. ferror() verifica errores. perror() imprime un mensaje de error descriptivo.

Estas funciones son cruciales para el manejo robusto de errores en operaciones de E/S. feof() determina si se ha alcanzado el final del archivo, diferenciando entre una lectura fallida y el final legítimo de los datos. Es esencial en bucles de lectura para terminar apropiadamente sin generar errores espurios. ferror() detecta si ha ocurrido un error de E/S en el flujo, como problemas de hardware, permisos insuficientes, o espacio en disco agotado. Ambas funciones mantienen estado interno hasta que se llame a clearerr(). perror() es invaluable para debugging, imprimiendo un mensaje descriptivo del último error basado en errno, precedido por el texto proporcionado. Juntas, estas funciones permiten distinguir entre diferentes condiciones de terminación y proporcionar retroalimentación significativa al usuario sobre problemas de E/O.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *archivo = fopen("datos.txt", "r");
    if (archivo == NULL) {
        perror("Error al abrir datos.txt");
        return 1;
    }

    // Leemos el archivo carácter por carácter
    while (1) {
        int c = fgetc(archivo);

        if (feof(archivo)) { // Verificamos si llegamos al final
            printf("\nFin del archivo alcanzado.\n");
            break;
        }
        if (ferror(archivo)) { // Verificamos si hubo un error de lectura
            fprintf(stderr, "Error de lectura en el archivo.\n");
            break;
        }

        putchar(c); // Imprimimos el carácter leído
    }

    fclose(archivo);
    return 0;
}

“Manejo de fin de archivo y errores de lectura.”


<stdlib.h> - Utilidades Generales

El header <stdlib.h> proporciona funciones para gestión de memoria dinámica, conversión de cadenas, generación de números aleatorios, control de programa y más.

Gestión de Memoria Dinámica

malloc, calloc, realloc y free

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);

malloc() asigna memoria sin inicializar. calloc() asigna e inicializa a cero. realloc() redimensiona. free() libera memoria.

Estas cuatro funciones constituyen el núcleo de la gestión de memoria dinámica en C, permitiendo a los programas solicitar y liberar memoria en tiempo de ejecución. malloc(size) reserva un bloque contiguo de size bytes sin inicializar, siendo la función más básica y eficiente para asignación. calloc(nmemb, size) no solo asigna memoria sino que garantiza que todos los bytes estén inicializados a cero, útil cuando se necesita memoria limpia para estructuras o arreglos. realloc(ptr, size) redimensiona un bloque previamente asignado, pudiendo expandirlo o contraerlo, y maneja automáticamente la copia de datos cuando es necesario mover el bloque. free(ptr) devuelve la memoria al sistema, siendo crucial para prevenir fugas de memoria. El uso correcto de estas funciones es fundamental para crear programas robustos y eficientes en memoria.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    int *numeros = malloc(10 * sizeof(int));
    if (numeros == NULL) {
        fprintf(stderr, "Error: fallo al asignar memoria\n");
        return 1;
    }
    
    // Usar la memoria...
    for (int i = 0; i < 10; i++) {
        numeros[i] = i * 2;
    }
    
    free(numeros);
    return 0;
}

“Uso básico de malloc y free”

Conversión de Cadenas

atoi, atol y strtol, strtod

int atoi(const char *nptr);
long strtol(const char *nptr, char **endptr, int base);
double strtod(const char *nptr, char **endptr);

atoi() es simple pero sin manejo de errores. strtol() y strtod() permiten detectar errores.

Las funciones de conversión de cadenas son esenciales para el procesamiento de entrada de usuario y datos textuales. atoi() (ASCII to Integer) es la función más simple, convirtiendo cadenas a enteros, pero carece de mecanismos robustos de detección de errores, lo que la hace inadecuada para aplicaciones críticas. strtol() (String to Long) es mucho más sofisticada, permitiendo especificar la base numérica (2-36), detectar errores de conversión y obtener un puntero al primer carácter no convertido. strtod() (String to Double) proporciona capacidades similares para números de punto flotante. Estas funciones “str” son fundamentales en parsers, interfaces de línea de comandos y cualquier aplicación que necesite conversión robusta entre representaciones textuales y numéricas de datos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Uso: %s <numero>\n", argv[0]);
        return EXIT_FAILURE;
    }

    char *endptr;
    long numero = strtol(argv[1], &endptr, 10);

    // Verificamos si hubo un error de conversión
    // 1. ¿endptr apunta al inicio? -> No se encontró ningún número
    // 2. ¿*endptr no es el final de la cadena? -> Había caracteres extra
    if (endptr == argv[1] || *endptr != '\0') {
        fprintf(stderr, "Error: '%s' no es un número entero válido.\n", argv[1]);
        return EXIT_FAILURE;
    }

    printf("El doble del número es: %ld\n", numero * 2);
    return EXIT_SUCCESS;
}

“Uso robusto de strtol para validación.”

Números Aleatorios

rand y srand

int rand(void);
void srand(unsigned int seed);

rand() genera números pseudoaleatorios entre 0 y RAND_MAX. srand() inicializa el generador.

El sistema de generación de números aleatorios de C está basado en algoritmos deterministas que producen secuencias pseudoaleatorias reproducibles. rand() utiliza un generador de congruencia lineal que produce números en el rango [0, RAND_MAX], donde RAND_MAX es al menos 32767. srand(seed) inicializa el estado interno del generador con una semilla, permitiendo control sobre la reproducibilidad: la misma semilla siempre produce la misma secuencia. Es crucial llamar a srand() una sola vez al inicio del programa, típicamente con time(NULL) para obtener secuencias diferentes en cada ejecución.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include <stdio.h>
#include <time.h> // Necesario para time()

int main(void)
{
    // Usamos la hora actual como semilla. Esto solo se hace UNA VEZ por programa.
    srand((unsigned)time(NULL));
    
    printf("5 números aleatorios entre 1 y 100:\n");
    for (int i = 0; i < 5; i++) {
        // rand() % 100 -> número entre 0 y 99
        // + 1          -> número entre 1 y 100
        int numero_aleatorio = (rand() % 100) + 1;
        printf("%d\n", numero_aleatorio);
    }
    
    return 0;
}

“Generando 5 números aleatorios entre 1 y 100.”

Búsqueda y Ordenamiento

qsort y bsearch

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));
void *bsearch(const void *key, const void *base, size_t nmemb,
              size_t size, int (*compar)(const void *, const void *));

qsort() ordena un arreglo. bsearch() busca en un arreglo ordenado.

Estas funciones implementan algoritmos fundamentales de ciencias de la computación con interfaces genéricas y eficientes. qsort() utiliza una variante del algoritmo quicksort para ordenar arreglos de cualquier tipo de datos, requiriendo una función de comparación definida por el usuario que determina el orden. Su complejidad promedio es O(nlogn)O(n log n), siendo uno de los algoritmos de ordenamiento más eficientes para uso general. bsearch() implementa búsqueda binaria en arreglos previamente ordenados (típicamente por qsort()), con complejidad O(logn)O(log n), muchísimo más rápida que búsqueda lineal para datos grandes. Ambas funciones usan punteros genéricos (void*) y funciones de comparación, permitiendo trabajar con cualquier tipo de datos. Son pilares fundamentales para aplicaciones que manejan grandes volúmenes de datos, bases de datos en memoria, y algoritmos que requieren acceso eficiente a información ordenada.

Aritmética de Enteros

abs, labs, llabs

Calculan el valor absoluto de un número entero.

div, ldiv, lldiv

Realizan la división entera, devolviendo el cociente y el resto en una única operación.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>

int main() {
    int numero = -150;
    printf("El valor absoluto de %d es %d.\n", numero, abs(numero));

    int dividendo = 17;
    int divisor = 5;

    // Usamos la función div para obtener cociente y resto.
    div_t resultado = div(dividendo, divisor);

    printf("\nAl dividir %d entre %d:\n", dividendo, divisor);
    printf("  - Cociente: %d\n", resultado.quot); // Equivalente a 17 / 5
    printf("  - Resto: %d\n", resultado.rem);   // Equivalente a 17 % 5

    return EXIT_SUCCESS;
}

“Uso de funciones de valor absoluto y división.”

Control del Programa

exit, abort y atexit

void exit(int status);
void abort(void);
int atexit(void (*func)(void));

exit() termina el programa normalmente. abort() termina abruptamente. atexit() registra funciones de limpieza.

Estas funciones controlan el ciclo de vida del programa de maneras fundamentalmente diferentes. exit() realiza una terminación ordenada, ejecutando todas las funciones registradas con atexit(), vaciando buffers de salida, cerrando archivos abiertos, y devolviendo un código de estado al sistema operativo (típicamente 0 para éxito, no-cero para error). abort() causa terminación inmediata y anormal, generalmente produciendo un core dump para debugging y sin ejecutar limpieza. Se usa cuando el programa detecta un estado irrecuperable. atexit() permite registrar hasta 32 funciones que se ejecutarán automáticamente en terminación normal, en orden LIFO (último registrado, primero ejecutado). Es esencial para limpieza de recursos, cierre de conexiones, y otras tareas de finalización que garanticen que el programa termine limpiamente.

Interacción con el Entorno

getenv

Obtiene el valor de una variable de entorno.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

int main() {
    // Intentamos obtener la variable de entorno 'USER' (común en Linux/macOS)
    // o 'USERNAME' (común en Windows).
    char *usuario = getenv("USER");
    if (usuario == NULL) {
        usuario = getenv("USERNAME");
    }

    if (usuario != NULL) {
        printf("Hola, %s!\n", usuario);
    } else {
        printf("No se pudo determinar el nombre de usuario.\n");
    }

    char *path = getenv("PATH");
    if (path != NULL) {
        printf("\nLa variable PATH del sistema es:\n%s\n", path);
    }

    return EXIT_SUCCESS;
}

“Leyendo las variables de entorno USER y PATH.”

system

Ejecuta un comando del sistema operativo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("Listando archivos del directorio actual:\n");
    // En sistemas POSIX (Linux, macOS) se usa "ls", en Windows es "dir".
    #ifdef _WIN32
        system("dir");
    #else
        system("ls -l");
    #endif

    int divisor = 0;
    if (divisor == 0) {
        fprintf(stderr, "Error crítico: intento de división por cero. Saliendo...\n");
        exit(EXIT_FAILURE); // Termina el programa inmediatamente
    }

    // Este código nunca se ejecuta
    printf("Esto no se imprimirá.\n");
    return EXIT_SUCCESS;
}

“Uso de exit y system.”


<string.h> - Manipulación de Cadenas y Memoria

El header <string.h> proporciona funciones para manipular cadenas (arreglos de caracteres terminados en '\0') y bloques de memoria.

Longitud y Copia

strlen, strcpy y strncpy

size_t strlen(const char *s);
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);

strlen() retorna la longitud. strcpy() copia sin verificar límites. strncpy() copia con límite (pero no garantiza terminación nula).

Estas tres funciones son fundamentales en el manejo de cadenas C, pero requieren uso cuidadoso. strlen() recorre la cadena hasta encontrar el carácter nulo ‘\0’, devolviendo el número de caracteres (excluyendo el terminador). Su complejidad O(n) significa que llamarla repetidamente en bucles es ineficiente. strcpy() es notoriamente peligrosa porque copia ciegamente desde origen a destino sin verificar límites, siendo una fuente común de vulnerabilidades de buffer overflow. strncpy() es más segura al limitar la copia a n caracteres, pero tiene la peculiaridad de no garantizar terminación nula si la cadena origen tiene exactamente n o más caracteres. Para uso seguro, siempre debe asegurarse manualmente la terminación nula después de usar strncpy().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    char destino[20]; // Un búfer con espacio para 19 chars + '\0'
    char origen[] = "¡Hola Mundo!";

    // Copia segura con strncpy
    strncpy(destino, origen, sizeof(destino) - 1);
    destino[sizeof(destino) - 1] = '\0'; // Aseguramos la terminación nula

    printf("Después de strncpy: %s\n", destino);

    // Concatenación segura con strncat
    // Dejamos espacio para 3 caracteres más y el nulo.
    strncat(destino, " Adiós.", 3); // Solo añadimos " Ad"

    printf("Después de strncat: %s\n", destino);

    return EXIT_SUCCESS;
}

“Uso seguro de strncpy y strncat.”

Comparación y Búsqueda

strcmp, strstr y strchr

int strcmp(const char *s1, const char *s2);
char *strstr(const char *haystack, const char *needle);
char *strchr(const char *s, int c);

strcmp() compara cadenas. strstr() busca subcadena. strchr() busca carácter.

Estas funciones implementan algoritmos esenciales de búsqueda y comparación de cadenas. strcmp() realiza comparación lexicográfica byte por byte, retornando un valor negativo si la primera cadena es “menor”, cero si son iguales, o positivo si la primera es “mayor”. Es fundamental para ordenamiento y búsquedas que requieren orden alfabético. strstr() implementa búsqueda de subcadenas usando algoritmos eficientes, devolviendo un puntero a la primera ocurrencia de la subcadena buscada o NULL si no se encuentra. Es esencial en procesamiento de texto, parsing y filtrado de contenido. strchr() busca la primera ocurrencia de un carácter específico, siendo más eficiente que strstr() cuando se busca un solo carácter. Estas funciones son pilares de algoritmos de procesamiento de texto y forman la base de sistemas de búsqueda más complejos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    char frase[] = "El zorro marrón rápido salta sobre el perro perezoso.";
    char *subcadena;
    char letra = 'p';

    // Buscamos la subcadena "rápido"
    subcadena = strstr(frase, "rápido");
    if (subcadena != NULL) {
        printf("Subcadena encontrada: %s\n", subcadena);
    }

    // Buscamos la primera 'p'
    char* primera_p = strchr(frase, letra);
    if(primera_p != NULL){
        printf("La primera '%c' está en la posición: %ld\n", letra, primera_p - frase);
    }

    // Buscamos la última 'p'
    char* ultima_p = strrchr(frase, letra);
    if(ultima_p != NULL){
        printf("La última '%c' está en la posición: %ld\n", letra, ultima_p - frase);
    }

    return EXIT_SUCCESS;
}

“Buscando caracteres y subcadenas.”

Manipulación de Memoria

memcpy, memmove, memset y memcmp

void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
void *memset(void *s, int c, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);

memcpy() copia bytes (sin solapamiento). memmove() copia con solapamiento. memset() llena con un valor. memcmp() compara bytes.

Las funciones de manipulación de memoria operan a nivel de bytes individuales, siendo más fundamentales que las funciones de cadenas. memcpy() es la función de copia más eficiente, transfiriendo n bytes desde origen a destino, pero requiere que las regiones de memoria no se solapen. memmove() es similar pero maneja correctamente el solapamiento de memoria, copiando a través de un buffer temporal si es necesario, lo que la hace más segura pero potencialmente más lenta. memset() es extremadamente útil para inicialización, llenando un bloque de memoria con un valor específico (típicamente 0 para limpiar estructuras). memcmp() compara bloques de memoria byte por byte, siendo más rápida que strcmp() cuando se conoce la longitud exacta. Estas funciones son fundamentales para manipulación de estructuras, inicialización de arreglos y operaciones de bajo nivel donde se requiere control preciso sobre la memoria.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <string.h>

struct Punto { int x; int y; };

int main() {
    struct Punto p1 = {10, 20};
    struct Punto p2;

    // Usar memset para inicializar una estructura a cero
    memset(&p2, 0, sizeof(struct Punto));
    printf("p2 inicializada con memset: x=%d, y=%d\n", p2.x, p2.y);

    // Usar memcpy para copiar una estructura
    memcpy(&p2, &p1, sizeof(struct Punto));
    printf("p2 después de memcpy: x=%d, y=%d\n", p2.x, p2.y);

    // Usar memcmp para comparar dos bloques de memoria
    if (memcmp(&p1, &p2, sizeof(struct Punto)) == 0) {
        printf("p1 y p2 son idénticas.\n");
    }

    // Ejemplo de memmove con solapamiento
    char buffer[] = "123456789";
    printf("\nBuffer original: %s\n", buffer);
    // Mover "12345" tres posiciones a la derecha
    memmove(buffer + 3, buffer, 5);
    printf("Buffer después de memmove: %s\n", buffer);

    return 0;
}

“Uso de funciones de manipulación de memoria.”


<time.h> - Manejo de Tiempo y Fecha

El header <time.h> proporciona tipos y funciones para manipular tiempo y fecha.

Tipos y Obtención de Tiempo

time_t, struct tm, time y clock

typedef /* ... */ time_t;
struct tm { int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, ...; };

time_t time(time_t *tloc);
clock_t clock(void);

time() retorna el tiempo actual. clock() retorna el tiempo de procesador.

Estas funciones proporcionan acceso a diferentes aspectos del tiempo del sistema. time() obtiene el tiempo actual como un valor time_t, que típicamente representa segundos transcurridos desde el “epoch” Unix (1 enero 1970, 00:00:00 UTC). Es fundamental para timestamping, logging, y cálculos de duración. Si se pasa un puntero no-NULL, también almacena el valor en esa ubicación. clock() mide el tiempo de procesador consumido por el programa, retornando un valor clock_t que debe dividirse por CLOCKS_PER_SEC para obtener segundos. Es esencial para benchmarking y profiling de rendimiento, ya que mide tiempo de CPU real en lugar de tiempo de reloj de pared, ignorando tiempo bloqueado en I/O o esperando otros procesos.

Conversión y Formateo

localtime, strftime y difftime

struct tm *localtime(const time_t *timer);
size_t strftime(char *s, size_t maxsize, const char *format, 
                const struct tm *timeptr);
double difftime(time_t time1, time_t time0);

localtime() convierte a hora local. strftime() formatea según especificadores. difftime() calcula diferencia en segundos.

Estas funciones forman el núcleo de conversión y formateo de tiempo en C. localtime() descompone un valor time_t en una estructura tm ajustada a la zona horaria local y horario de verano, siendo esencial para mostrar fechas y horas comprensibles para el usuario. Importante: retorna un puntero a memoria estática que se sobrescribe en llamadas subsecuentes. strftime() es extremadamente poderosa para formateo personalizado, usando especificadores como %Y (año), %m (mes), %d (día), permitiendo crear representaciones de fecha/hora en cualquier formato deseado. difftime() calcula la diferencia en segundos entre dos valores time_t, manejando apropiadamente los casos edge y diferentes representaciones internas de tiempo. Juntas, estas funciones permiten manipulación completa de fechas y horas para logging, interfaces de usuario y cálculos temporales.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <time.h>

int main(void)
{
    time_t ahora = time(NULL);
    struct tm *tiempo = localtime(&ahora);
    char buffer[100];
    
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tiempo);
    printf("Fecha: %s\n", buffer);
    
    return 0;
}

Headers Adicionales (Resumen)

Otros headers importantes incluyen:


Conclusión y Buenas Prácticas

La biblioteca estándar de C es esencial para escribir código portable y robusto. Recomendaciones clave:

  1. Siempre verificá valores de retorno de funciones que pueden fallar

  2. Usá tipos de <stdint.h> cuando el tamaño sea crítico para portabilidad

  3. Preferí funciones seguras como snprintf(), fgets() sobre sprintf() y gets()

  4. Inicializá variables y estructuras completamente para evitar valores indefinidos

  5. Gestioná recursos cuidadosamente (archivos, memoria) siguiendo el patrón de inicializar, usar y liberar (regla Regla 0x001Ah: Liberá siempre la memoria dinámica y prevení punteros colgantes)

  6. Documentá suposiciones con aserciones durante desarrollo


Glosario de Términos

Análisis Léxico (Lexical Analysis)
Fase inicial de un compilador que convierte una secuencia de caracteres (código fuente) en una secuencia de “tokens” o componentes con significado propio (palabras clave, identificadores, etc.). Es sinónimo de tokenización.
Benchmarking
Proceso de ejecutar pruebas estandarizadas para medir y comparar el rendimiento (velocidad, uso de memoria) de un programa o una función.
Buffer
Zona de memoria temporal usada para almacenar datos mientras se transfieren de un lugar a otro, optimizando operaciones de entrada/salida.
Buffer Overflow (Desbordamiento de Búfer)
Error de seguridad que ocurre cuando se escriben datos más allá de los límites de un buffer, sobrescribiendo memoria adyacente y pudiendo causar fallos o vulnerabilidades.
Comparación Lexicográfica
Comparación de dos cadenas de texto basada en el orden alfabético de sus caracteres, similar al orden de las palabras en un diccionario.
Core Dump
Archivo que guarda el estado de la memoria de un programa en el momento en que terminó de forma anormal. Se usa para depurar errores complejos post-mortem.
Epoch (Época)
Un instante de tiempo que sirve como punto de referencia. En sistemas Unix/POSIX, el epoch es el 1 de enero de 1970 a las 00:00:00 UTC. time_t suele medir los segundos transcurridos desde ese momento.
Invariante
Condición o propiedad que debe mantenerse siempre verdadera en un punto específico de la ejecución de un programa. Las aserciones (assert) son una forma de verificar invariantes durante el desarrollo.
Locale (Configuración Regional)
Conjunto de parámetros que definen el idioma, país y otras convenciones culturales (formato de fecha, moneda, separador decimal, codificación de caracteres) para adaptar el comportamiento de un programa.
Macro
Fragmento de código identificado por un nombre, que es sustituido por su contenido por el preprocesador antes de la compilación. Se definen con la directiva #define.
Padding (Relleno)
Bytes extra que el compilador inserta entre los miembros de una estructura para alinear cada miembro en una dirección de memoria que sea múltiplo de su tamaño. Esto optimiza la velocidad de acceso a la memoria.
Parsing (Análisis Sintáctico)
Proceso de analizar una secuencia de tokens para determinar su estructura gramatical. El objetivo es construir una representación interna (como un árbol sintáctico) que el programa pueda entender y procesar.
Precondición
Condición que debe ser verdadera antes de que se llame a una función para que esta pueda operar correctamente. assert se usa comúnmente para verificar precondiciones.
Profiling (Perfilado)
Análisis del comportamiento de un programa en tiempo de ejecución para medir su uso de recursos, como el tiempo de CPU o la memoria utilizada por cada función. Ayuda a identificar cuellos de botella de rendimiento.
Serialización
Proceso de convertir una estructura de datos en un formato (generalmente una secuencia de bytes) que puede ser almacenado en disco o transmitido por red, para su posterior reconstrucción.
Timestamping (Sellado de Tiempo)
Acción de registrar la fecha y hora en que ocurre un evento.
Tokenización
Proceso de dividir una secuencia de texto en “tokens” o unidades léxicas (palabras, números, símbolos). Es el primer paso del análisis léxico.