Introducción a C¶
C es un lenguaje de programación desarrollado en el año 1972 por Dennis Ritchie. El lenguaje C es un lenguaje estructurado el cual a pesar de ser un lenguaje de "alto nivel", da acceso a hacer uso de la memoria, registros, etc.
C es un lenguaje compilado; se compila una vez para cada arquitectura y se genera un binario que se puede ejecutar directamente. Para poder compilar y ejecutar un archivo llamado HelloWorld.c, se realiza de la siguiente manera:
$ gcc HelloWorld.c // se compila el archivo $ ./a.out // se ejecuta el programa $ Hello World // output del programa, en este caso
Notese que el compilador que estamos utilizando se llama gcc. gcc* se encuentra por defecto en la mayoria de sistemas operativos con kernel linux. Cuando nosotros no especificamos un nombre para el archivo de salida, cgg lo nombra, por defecto, a.out**.
Si queremos especificar un nombre para el archivo de salida entonces podemos utilizar la bandera -o:
$ gcc -o Hello HelloWorld.c // se compila el archivo $ ./Hello // se ejecuta el programa $ Hello World // output del programa, en este caso
Comentarios en c¶
// para realizar comentarios de una línea /* TODO LO QUE ESTE DENTRO DE ESTA PARTE ES UN COMENTARIO */
Tipos de declaración¶
Las variables deben ser declaradas antes de usarse y el tipo de la variable no puede ser cambiado. En C, el tamaño de la variable depende de la arquitectura en la cual se este trabajando (x_86, x_86_84, ARM, RISC-V, etc.).
Algo muy importante que diferencia lenguajes como Java de C, es que en C, si no se inicializa el valor de la variable, la podemos utilizar sin problemas, pero contendrá información "basura", es decir, los datos que estaban ubicados previamente en la dirección de memoria en donde la variable fue guardada.
Declaración de constantes¶
#define CONSTANTE_DEFINE 25 enum T_ENUM { CONSTANTE_ENUM = 25 }; const int CONSTANTE_VARIABLE = 25;
Valores Booleanos en C¶
Es un importante mencionar que en C, las variables tipo booleanas no existen. En vez de eso, todo valor diferente de 0 se considera verdadero, es decir si se desea representar el valor falso se debe utilizar un 0.
Funciones en C¶
- En la declaración de la función debe ir el tipo de dato del valor de retorno y de todos los argumentos.
- void para declarar funciones sin nigun retorno.
- Las funciones en C deben ser declaradas antes de ser usadas; esto implica que tendremos problemas con funciones de este tipo:
int funcion1(int x){ if(x < 10){ return function2(x); } return x+1; } int funcion2(int x){ if(x>= 10){ return function1(x); } return x-1; }
Noten que aqui function1(int x) ejecuta function2(int x), pero function2(int x) esta definida abajo de function1(int x). Para solucionar este problema, declaramos las funciones antes de definirlas:
int function1(int x); int function2(int x); int funcion1(int x){ if(x < 10){ return function2(x); } return x+1; } int funcion2(int x){ if(x>= 10){ return function1(x); } return x-1; }
Ejemplo: Función que retorna un valor entero.¶
int funcion1(){ x = 10; return x; }
Ejemplo: Función que no tiene un valor de retorno y que recibe un entero como argumento.¶
void function2(int x){ printf("Hola Mundo %d!", x); }
Estructuras en c¶
Es un tipo de dato compuesto el cual permite almacenar un conjunto de datos de tipos diferentes. Es muy similar a lo que en java se conoce como clases, pero sin métodos.
Ejemplo:¶
int main(void){ /*definimos una estructura en C llamada Point que contiene 2 variables de tipo entero. */ typedef struct { int x, y; } Point; Point p1; //se define una variable de tipo Point llamada p1 //asignanción de valores a las variables x y y de la variable p1. p1.x = 2; p1.y = 4; return 0; }
Punteros en c¶
Un puntero es una variable la cual guarda la dirección de memoria de otra variable o de algún dato. Es necesario al igual que una simple variable asignar el tipo que representa.
Ejemplo:¶
int *pi; //declaración de un puntero de datos enteros. char *pc; //declaración de un puntero dé datos tipo char.
Ejemplo: Puntero de tipo arbitrario.¶
//definición de un puntero el cual puede ser asignado a diferentes tipos. void *vp;
Podemos incluso definir un puntero que apunta a un puntero
Ejemplo: Puntero de Punteros¶
//definición una variable, un puntero que apunta a ella, y un puntero que apunta a este puntero char c = '1'; char *p = &c; char **args = &p;
Ejemplo: Asignar Valores Usando Punteros¶
int x = 10; int *pi = &x; //al puntero pi se le asigna la dirección de memoria de la variable x. int valorx = *pi; // en la variable valorx se almacena el valor al cual apunta la variable pi, en este ejemplo es 10
Para poder modificar el valor de una variable x declarada en una función A desde una función B, enviamos un puntero que apunta a x como argumento a la función B:
Ejemplo:¶
void addOne(int* x){ (*x)++; } int functionA(){ int x = 10; addOne(&x); return x; }
Noten el usuo de los paréntesis, sin los paréntesis, aumentariamos en 1 el puntero, pero al agregarlos, aumentamos en 1 el valor al que apunta el puntero
Manejo de Memoria en C¶
Podemos diferenciar varias estructuras de memoria existentes al momento de ejecutar un programa: - Àrea de Texto: Almacena las instrucciones que estamos ejecutando - Àrea de Datos: Almacena datos como numeros y strings que son constantes globales en nuestro programa - Stack: Almacena datos estaticos como variables, arreglos de tamaño predefinito, etc. Estos datos tienen la caracteristica de que no existen en todo momento de la ejecución del programa. Por ejemplo, una variable x definida en cierta función solo existirá cuando esa función se este ejecutando. Algunas cosas a tomar en cuenta sobre el stack, aunque no las veremos directamente en C, son las siguientes: - Tiene 3 operaciones básicas de un stack: push, pop, top (observar que hay) - CUIDADO con la ubicacion del Stack; en assembler veremos que el Stack crece habia abajo, lo que significa que cuando agregamos un dato, la dirección de memoria disminuye - A nivel ensamblador existe un registro llamado Stack Pointer o SP. Probablemente el registro más usado en assembler - Las variables locales y los parámetros de las funciones se guardan en el Stack - La dirección de retorno tambien se guarda en el Stack - El compilador es el que se encarga de pedir memoria en cada llamada y devolverla cuando se termina, es decir, el Stack es "transparente" al programador de C
- Heap: Almacena datos dinámicos, como por ejemplo, un arreglo del tamaño indicado por el usuario. En C, el Heap es la unica estructura de memoria que es administrada directamente por el programador (reservar, liberar y respetar el espacio asignado). El heap, a diferencia del Stack, crece a direcciones de memorias más altas.
Manejo del Heap¶
type* ptr = (type*) malloc(byte-size); // prt es un puntero
// ptr es puntero a un "bloque" de 100 enteros. int* ptr = (int*) malloc(100 * sizeof(int));
malloc()
: los devuelve un bloque de memoria. No los inicializa con ningún valor.
- función calloc()
: bloque de memoria inicializada con ceros. Es una operación lenta. Evitar usarla en cuanto sea posible.
- función free()
: libera un bloque de memoria que previamente pedimos.
- cuidado con usar malloc sin el free. Aquí no hay garbage collector.
- Puede ser lento buscar espacio disponible
- Recordar que manejar el heap es tarea del programador.
Uso de Valgrid¶
Se puede descargar de: http://valgrind.org/ - Ayuda a la depuración de problemas de memoria. Puede servir para detectar problemas como fugas de memoria (memory leak).
Clasificación de Bugs¶
- Bohr Bugs
- sabemos en precisamente en donde está (como los electones en el modelo atómico de Bohr)
- Fáciles de reproducir
- Heinsenbugs
- Difíciles de diagnosticar
- Dificiles de reproducir