Este documento detalla conceptos fundamentales de programación en C, incluyendo operaciones a nivel de bit, manipulación de memoria, manejo de cadenas y la estructura de datos de pila (stack).
Operaciones a Nivel de Bit Las operaciones a nivel de bit manipulan los datos en su representación binaria. Los operadores clave son:
<<(Desplazamiento a la izquierda): Mueve los bits de un número a la izquierda, rellenando con ceros a la derecha.>>(Desplazamiento a la derecha): Mueve los bits de un número a la derecha. El comportamiento para números con signo puede variar (relleno con signo o cero).&(AND a nivel de bit): Devuelve 1 en una posición de bit si ambos operandos tienen 1 en esa posición.|(OR a nivel de bit): Devuelev 1 en una posición de bit si al menos uno de los operandos tiene 1.^(XOR a nivel de bit): Devuelve 1 en una posición de bit si los operandos tienen bits diferentes en esa posición.~(NOT a nivel de bit): Invierte todos los bits del operando.
Representación de Números Los números enteros en C se almacenan internamente utilizando el complemento a dos:
- Representación directa (源码): La representación binaria estándar del número. Para negativos, el bit más significativo indica el signo.
- Complemento a uno (反码): Se obtiene invirtiendo todos los bits de la representación directa (para positivos) o invirtiendo los bits excepto el signo (para negativos).
- Complemento a dos (补码): Se obtiene sumando 1 al complemento a uno. Todos los números (positivos y negativos) se almacenan en memoria como complemento a dos. Los números positivos tienen la misma representación directa, complemento a uno y complemento a dos.
Manejo de Datos y Memoria
Conversión y Tamaño de Datos
- La función
strlen()(de<string.h></string.h>) se utiliza para determinar la longitud de una cadena de caracteres (sin incluir el carácter nulo terminador). - La función
sizeof()se usa para obtener el tamaño en bytes de un tipo de dato o variable.
Estructuras y Punteros Las estructuras permiten agrupar variables de diferentes tipos bajo un mismo nombre. Los punteros se utilizan para referenciar la dirección de memoria de estas estructuras.
struct DatosUsuario {
char nombre[50];
int edad;
char genero[10];
int identificador;
};
struct DatosUsuario usuario1 = {"Ana López", 25, "Femenino", 1001};
struct DatosUsuario usuario2;
struct DatosUsuario* ptrUsuario = &usuario1; // Puntero a usuario1
// Acceso a través del puntero usando el operador ->
printf("Nombre del usuario: %s\n", ptrUsuario->nombre);
// Acceso a través del puntero usando desreferenciación y operador punto
printf("Edad del usuario: %d\n", (*ptrUsuario).edad);
// Para modificar un campo a través del puntero:
strcpy(usuario1.nombre, "Ana Garcia"); // strcpy es de <string.h>
printf("Nombre modificado: %s\n", usuario1.nombre);
Gestión de Memoria y Alcance
- Las variables locales declaradas dentro de una función residen en la pila (stack). Las direcciones de estas variables no son válidas una vez que la función termina.
- El uso de la palabra clave
staticpara una variable local extiende su ciclo de vida hasta el final del programa, manteniendo su valor entre llamadas a la función. La varible se almacenará en la memoria estática en lugar de la pila. - Los datos asignados dinámicamente en el montículo (heap) usando
malloc,calloc, etc., persisten hasta que se liberan explícitamente confree.
Funciones de Utilidad
Validación de Punteros La macro assert() (de <assert.h></assert.h>) se utiliza para verificar condiciones durante la ejecución. Si la condición es falsa, el programa termina y se muestra un mensaje de error.
char* buffer = obtenerDatos(); // Asume que esta función podría devolver NULL
assert(buffer != NULL && "Error: No se pudieron obtener los datos.");
// Continuar si buffer no es NULL
Ordenamiento (Quick Sort) La función qsort() (de <stdlib.h></stdlib.h>) implementa el algoritmo de ordenamiento rápido. Requiere una función de comparación personalizada.
// Función de comparación para enteros
int compararEnteros(const void* elem1, const void* elem2) {
int val1 = *(const int*)elem1;
int val2 = *(const int*)elem2;
if (val1 < val2) return -1;
if (val1 > val2) return 1;
return 0;
}
// Ejemplo de uso
int numeros[] = {5, 2, 8, 1, 9};
size_t tamano = sizeof(numeros) / sizeof(numeros[0]);
qsort(numeros, tamano, sizeof(int), compararEnteros);
printf("Array ordenado: ");
for (size_t i = 0; i < tamano; ++i) {
printf("%d ", numeros[i]);
}
printf("\n");
// Función de comparación para flotantes (con manejo de precisión)
int compararFlotantes(const void* elem1, const void* elem2) {
float val1 = *(const float*)elem1;
float val2 = *(const float*)elem2;
if (val1 < val2) return -1;
if (val1 > val2) return 1;
return 0;
}
Alineación de Estructuras La directiva de preprocesador #pragma pack() se puede usar para controlar la alineación de los miembros de una estructura, lo cual afecta el tamaño y la disposición en memoria.
#pragma pack(push, 1) // Establece la alineación a 1 byte
struct PaqueteDatos {
char tipo; // 1 byte
int valor; // 4 bytes (en sistemas de 32 bits)
short id; // 2 bytes
};
#pragma pack(pop) // Restaura la alineación predeterminada
// offsetof (de <stddef.h>) se usa para obtener el desplazamiento de un miembro
struct EjemploAlineacion {
char c1; // Offset 0
int i; // Offset 4 (si la alineación por defecto es 4)
double d; // Offset 8 (si la alineación por defecto es 8)
};
// printf("Offset de c1: %zu\n", offsetof(struct EjemploAlineacion, c1));
// printf("Offset de i: %zu\n", offsetof(struct EjemploAlineacion, i));
// printf("Offset de d: %zu\n", offsetof(struct EjemploAlineacion, d));
La alineación de estructuras puede afectar el rendimiento y el uso de memoria. El compilador generalmente añade bytes de relleno (padding) entre miembros para asegurar que cada miembro comience en una dirección de memoria que sea múltiplo de su tamaño o de la alineación natural del procesador.