0% encontró este documento útil (0 votos)
132 vistas42 páginas

Programacion en C Ejemplos Practicos

Programacion en c ejemplos practicos

Cargado por

aneudyh
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
132 vistas42 páginas

Programacion en C Ejemplos Practicos

Programacion en c ejemplos practicos

Cargado por

aneudyh
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 42

Contenidos

Artículos
Cadenas de caracteres 1
Manejo de archivos 3
Estructuras y Uniones 13
Punteros 24
Manejo dinámico de memoria 26
El proceso de compilación 30
Ejemplos 34
Glib 37

Referencias
Fuentes y contribuyentes del artículo 40

Licencias de artículos
Licencia 41
Cadenas de caracteres 1

Cadenas de caracteres
Las cadenas de caracteres (también llamadas cadenas o strings) son un tipo particular de vectores, son de hecho
vectores de char, con la particularidad que tienen una marca de fin (el caracter '\0'), además el lenguaje nos permite
escribirlas como texto dentro de comillas dobles. Veamos unos ejemplos de su declaración:

char cadena_hola[]="Hola";
char otro_hola[]={'H','o','l','a','\0'}; // Igual al anterior
char vector[]={'H','o','l','a'}; /* Un vector de 4 elementos,
con los elementos 'H','o','l' y 'a' */
char espacio_cadena[1024]="Una cadena en C";
char cadena_vacia[]="";

Cómo vimos anteriormente al declarar un vector se define la cantidad de elementos que puede contener, en el caso de
las cadenas se debe tener en cuenta el espacio adicional necesario para el \0. Viendo el ejemplo, tanto cadena_hola y
otro_hola tienen un largo 5 y cadena_vacia tiene un largo de 1.
También vimos anteriormente que al usar vectores debemos tener en cuenta su largo, y así es que el largo o cantidad
de elemento lo necesitamos en todas las funciones que definimos usando vectores y lo recibimos como un parámetro
más en estas, en el caso de las cadenas al tener una marca de fin podemos prescindir del largo y procesar una cadenas
hasta llegar a la marca de fin.
Por ejemplo, la siguiente función calcula el largo de una cadena:

/* devuelve la cantidad de caracteres en cadena sin contar el '\0' */


int largo_cadena(char cadena[])
{
int largo=0
while (cadena[largo]!='\0') largo++;
return largo;
}

Se debe tener en cuenta que el largo de una cadena y el largo del vector con la que se representa son distintos, tanto
por como largo_cadena() cuenta el largo de la cadena, como por espacio_cadena del ejemplo anterior.
Algo bastante usual es necesitar unir dos cadenas, veamos un ejemplo:

bool unir_cadenas(char destino[], char origen[], int largo)


{
int largo_origen = largo_cadena(origen);
int largo_destino = largo_cadena(destino);
if ( largo_origen+largo_destino+1 > largo ) {
return false;
}
for (int i=0; i<largo_origen;i++) {
destino[largo_destino+i] = origen[i];
}
destino[largo_destino+largo_origen]='\0';
return true;
}
...
if ( unir_cadenas(espacio_cadena," que puede crecer hasta 1023
Cadenas de caracteres 2

caracteres",1024) ) {
...

Estos dos ejemplos son versiones simplificadas de funciones provistas por la biblioteca estándar de C a través del
encabezado string.h. Nuestro largo_cadena() es similar al strlen() de la biblioteca estándar, y unir_cadenas() se
asemeja al strncat(). Si bien ver estas versiones nos sirven para entender las cadenas en C, en general será preferible
usar las funciones provistas por la biblioteca estándar, ya que podemos estar seguros que van a estar programadas de
la mejor manera posible.
Entre las funcione que provee la biblioteca estándar de C, las más importantes son:

largo = strlen(cadena) // Para obtener el largo de una cadena


strcpy(destino, origen) // Copia el contenido de origen en destino
// destino debe ser lo suficientemente grande
strcat(destino, origen) // Agrega el contenido de origen al final de
destino
// destino debe ser lo suficientemente grander
resultado = strcmp(cadena1, cadena2) // Compara dos cadenas
// devuelve un valor menor, igual o mayor que 0 según si
cadena1 es menor,
// igual o mayor que cadena2, respectivamente.
posicion = strchr(cadena, caracter) // Devuelve la posición en memoria
de la primer
// aparición de caracter dentro de cadena
posicion = strstr(cadena,subcadena) // Devuelve la posición en memoria
de la primer
// aparición de subcadena dentro de cadena

Veamos algunos ejemplos usando <string.h> :

#include <stdio.h>
#include <string.h>
...
char color[] = "rojo";
char grosor[] = "grueso";
...
char descripcion[1024];

strcpy(descripcion, "Lapiz color ");


strncat(descripcion, color, 1024);
strncat(descripcion, " de trazo ", 1024);
strncat(descripcion, grosor, 1024);
// descripcion contiene "Lapiz color rojo de trazo grueso"
...

void intercambiar(char vector[], int pos1, int pos2);


void invierte_cadena(char cadena[])
{
int largo = strlen(cadena);
for (int i=0; i < (largo/2); i++) {
Cadenas de caracteres 3

intercambiar(cadena, i, (largo-1)-i);
}
}
void intercambiar(char vector[], int pos1, int pos2)
{
char aux=vector[pos1];
vector[pos1]=vector[pos2];
vector[pos2]=aux;
}

Manejo de archivos
Así como hemos revisado la salida y entrada por pantalla y teclado respectivamente, veremos ahora la entrada y/o
salida de datos utilizando ficheros, lo cual será imprescindible para un gran número de aplicaciones que deseemos
desarrollar.

Ficheros
El estándar de C contiene funciones varias para la edición de ficheros, estas están definidas en la cabecera stdio.h y
por lo general empiezan con la letra f, haciendo referencia a file. Adicionalmente se agrega un tipo FILE, el cual se
usará como apuntador a la información del fichero. La secuencia que usaremos para realizar operaciones será la
siguiente:
• Crear un apuntador del tipo FILE *
• Abrir el archivo utilizando la función fopen y asignándole el resultado de la llamada a nuestro apuntador.
• Hacer las diversas operaciones (lectura, escritura, etc).
• Cerrar el archivo utilizando la función fclose.

fopen
Esta función sirve para abrir y crear ficheros en disco.
El prototipo correspondiente de fopen es:

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

Los parámetros de entrada de fopen son:


filename: una cadena que contiene un nombre de fichero válido. opentype: especifica el tipo de fichero que se abrirá
o se creará.
Una lista de parámetros opentype para la función fopen son:
• "r" : abrir un archivo para lectura, el fichero debe existir.
• "w" : abrir un archivo para escritura, se crea si no existe o se sobreescribe si existe.
• "a" : abrir un archivo para escritura al final del contenido, si no existe se crea.
• "r+" : abrir un archivo para lectura y escritura, el fichero debe existir.
• "w+" : crear un archivo para lectura y escritura, se crea si no existe o se sobreescribe si existe.
• "a+" : abrir/crear un archivo para lectura y escritura al final del contenido
Adicionalmente hay tipos utilizando "b" (binary) los cuales no serán mostrados por ahora y que solo se usan en los
sistemas operativos que no pertenecen a la familia de unix.
Manejo de archivos 4

fclose
Esta función sirve para poder cerrar un fichero que se ha abierto.
El prototipo correspondiente de fclose es:

int fclose (FILE *stream);

Un valor de retorno cero indica que el fichero ha sido correctamente cerrado, si ha habido algún error, el valor de
retorno es la constante EOF.
Un ejemplo pequeño para abrir y cerrar el archivo llamado fichero.in en modo lectura:

#include <stdio.h>

int main(int argc, char** argv)


{
FILE *fp;
fp = fopen ( "fichero.in", "r" );
fclose ( fp );

return 0;
}

Como vemos, en el ejemplo se utilizó el opentype "r", que es para la lectura.


Otra cosa importante es que el lenguaje C no tiene dentro de si una estructura para el manejo de excepciones o de
errores, por eso es necesario comprobar que el archivo fue abierto con éxito "if (archivo == NULL)". Si fopen pudo
abrir el archivo con éxito devuelve la referencia al archivo (FILE *), de lo contrario devuelve NULL y en este caso
se debera revisar la direccion del archivo o los permisos del mismo. En estos ejemplos solo vamos a dar una salida
con un retorno de 1 que sirve para señalar que el programa termino por un error.

feof
Esta función sirve para determinar si el cursor dentro del archivo encontró el final (end of file). Existe otra forma de
verificar el final del archivo que es comparar el caracter que trae fgetc del archivo con el macro EOF declarado
dentro de stdio.h, pero este método no ofrece la misma seguridad (en especial al tratar con los archivos "binarios").
La función feof siempre devolverá cero (Falso) si no es encontrado EOF en el archivo, de lo contrario regresará un
valor distinto de cero (Verdadero).
El prototipo correspondiente de feof es:

int feof(FILE *fichero);

rewind
Literalmente significa "rebobinar", sitúa el cursor de lectura/escritura al principio del archivo.
El prototipo correspondiente de rewind es:

void rewind(FILE *fichero);

Lectura
Un archivo generalmente debe verse como un string (una cadena de caracteres) que esta guardado en el disco duro.
Para trabajar con los archivos existen diferentes formas y diferentes funciones. Las funciones que podríamos usar
para leer un archivo son:
Manejo de archivos 5

• char fgetc(FILE *archivo)


• char *fgets(char *buffer, int tamano, FILE *archivo)
• size_t fread(void *puntero, size_t tamano, size_t cantidad, FILE *archivo);
• int fscanf(FILE *fichero, const char *formato, argumento, ...);
Las primeras dos de estas funciones son muy parecidas entre si. Pero la tercera, por el numero y el tipo de
parámetros, nos podemos dar cuenta de que es muy diferente, por eso la trataremos aparte junto al fwrite que es su
contraparte para escritura.

fgetc
Esta función lee un caracter a la vez del archivo que esta siendo señalado con el puntero *archivo. En caso de que la
lectura sea exitosa devuelve el caracter leído y en caso de que no lo sea o de encontrar el final del archivo devuelve
EOF.
El prototipo correspondiente de fgetc es:

char fgetc(FILE *archivo);

Esta función se usa generalmente para recorrer archivos de texto. A manera de ejemplo vamos a suponer que
tenemos un archivo de texto llamado "prueba.txt" en el mismo directorio en que se encuentra el fuente de nuestro
programa. Un pequeño programa que lea ese archivo será:

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *archivo;
char caracter;

archivo = fopen("prueba.txt","r");

if (archivo == NULL){

printf("\nError de apertura del archivo. \n\n");


}else{

printf("\nEl contenido del archivo de prueba es \n\n");

while (feof(archivo) == 0)
{
caracter = fgetc(archivo);
printf("%c",caracter);
}
}

return 0;
}
Manejo de archivos 6

fgets
Esta función está diseñada para leer cadenas de caracteres. Leerá hasta n-1 caracteres o hasta que lea un retorno de
línea. En este último caso, el carácter de retorno de línea también es leído.
El prototipo correspondiente de fgets es:

char *fgets(char *buffer, int tamaño, FILE *archivo);

El primer parámetro buffer lo hemos llamado así porque es un puntero a un espacio de memoria del tipo char
(podríamos usar un arreglo de char). El segundo parámetro es tamaño que es el limite en cantidad de caracteres a leer
para la funcion fgets. Y por ultimo el puntero del archivo por supuesto que es la forma en que fgets sabra a que
archivo debe leer.

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *archivo;

char caracteres[100];

archivo = fopen("prueba.txt","r");

if (archivo == NULL)
exit(1);

printf("\nEl contenido del archivo de prueba es \n\n");


while (feof(archivo) == 0)
{
fgets(caracteres,100,archivo);
printf("%s",caracteres);
}
system("PAUSE");
return 0;
}

Este es el mismo ejemplo de antes con la diferencia de que este hace uso de fgets en lugar de fgetc. La función fgets
se comporta de la siguiente manera, leerá del archivo apuntado por archivo los caracteres que encuentre y a ponerlos
en buffer hasta que lea un caracter menos que la cantidad de caracteres especificada en tamaño o hasta que encuentre
el final de una linea (\n) o hasta que encuentre el final del archivo (EOF). En este ejemplo no vamos a profundizar
mas que para decir que caracteres es un buffer, los pormenores seran explicados en la sección de manejo dinámico de
memoria.
El beneficio de esta función es que se puede obtener una linea completa a la vez. Y resulta muy útil para algunos
fines como la construcción de un parser de algún tipo de archivo de texto.
Manejo de archivos 7

fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
Esta función lee un bloque de una "stream" de datos. Efectúa la lectura de un arreglo de elementos "count", cada uno
de los cuales tiene un tamaño definido por "size". Luego los guarda en el bloque de memoria especificado por "ptr".
El indicador de posición de la cadena de caracteres avanza hasta leer la totalidad de bytes. Si esto es exitoso la
cantidad de bytes leídos es (size*count).
PARAMETROS:
ptr : Puntero a un bloque de memoria con un tamaño mínimo de (size*count) bytes.
size : Tamaño en bytes de cada elemento (de los que voy a leer).
count : Número de elementos, los cuales tienen un tamaño "size".
stream: Puntero a objetos FILE, que especifica la cadena de entrada.

fscanf
La función fscanf funciona igual que scanf en cuanto a parámetros, pero la entrada se toma de un fichero en lugar del
teclado.
El prototipo correspondiente de fscanf es:

int fscanf(FILE *fichero, const char *formato, argumento, ...);

Podemos ver un ejemplo de su uso, abrimos el documento "fichero.txt" en modo lectura y leyendo dentro de el.

#include <stdio.h>

int main ( int argc, char **argv )


{
FILE *fp;

char buffer[100];

fp = fopen ( "fichero.txt", "r" );

fscanf(fp, "%s" ,buffer);


printf("%s",buffer);

fclose ( fp );

return 0;
}
Manejo de archivos 8

Escritura
Así como podemos leer datos desde un fichero, también se pueden crear y escribir ficheros con la información que
deseamos almacenar, Para trabajar con los archivos existen diferentes formas y diferentes funciones. Las funciones
que podríamos usar para escribir dentro de un archivo son:
• int fputc(int caracter, FILE *archivo)
• int fputs(const char *buffer, FILE *archivo)
• size_t fwrite(void *puntero, size_t tamano, size_t cantidad, FILE *archivo);
• int fprintf(FILE *archivo, const char *formato, argumento, ...);

fputc
Esta función escribe un carácter a la vez del archivo que esta siendo señalado con el puntero *archivo. El valor de
retorno es el carácter escrito, si la operación fue completada con éxito, en caso contrario será EOF.
El prototipo correspondiente de fputc es:

int fputc(int carácter, FILE *archivo);

Mostramos un ejemplo del uso de fputc en un "fichero.txt", se escribira dentro del fichero hasta que presionemos la
tecla enter.

#include <stdio.h>

int main ( int argc, char **argv )


{
FILE *fp;

char caracter;

fp = fopen ( "fichero.txt", "r+" );

printf("\nIntrouce un texto al fichero: ");

while((caracter = getchar()) != '\n')


{
printf("%c", fputc(caracter, fp));
}

fclose ( fp );

return 0;
}
Manejo de archivos 9

fputs
La función fputs escribe una cadena en un fichero. No se añade el carácter de retorno de línea ni el carácter nulo
final. El valor de retorno es un número no negativo o EOF en caso de error. Los parámetros de entrada son la cadena
a escribir y un puntero a la estructura FILE del fichero donde se realizará la escritura.
El prototipo correspondiente de fputs es:

int fputs(const char *buffer, FILE *archivo)

para ver su funcionamiento mostramos el siguiente ejemplo:

#include <stdio.h>

int main ( int argc, char **argv )


{
FILE *fp;

char cadena[] = "Mostrando el uso de fputs en un fichero.\n";

fp = fopen ( "fichero.txt", "r+" );

fputs( cadena, fp );

fclose ( fp );

return 0;
}

fwrite
Esta función está pensada para trabajar con registros de longitud constante y forma pareja con fread. Es capaz de
escribir hacia un fichero uno o varios registros de la misma longitud almacenados a partir de una dirección de
memoria determinada. El valor de retorno es el número de registros escritos, no el número de bytes. Los parámetros
son: un puntero a la zona de memoria de donde se obtendrán los datos a escribir, el tamaño de cada registro, el
número de registros a escribir y un puntero a la estructura FILE del fichero al que se hará la escritura.
El prototipo correspondiente de fwrite es:

size_t fwrite(void *puntero, size_t tamano, size_t cantidad, FILE


*archivo);

Un ejemplo concreto del uso de fwrite con su contraparte fread y usando funciones es:

/*
* FicheroCompleto.c
*
* Copyright 2009 Julio César Brizuela <brizuela@linux-qxlk>
*
*/

#include <stdio.h>
Manejo de archivos 10

void menu();
void CrearFichero(FILE *Fichero);
void InsertarDatos(FILE *Fichero);
void VerDatos(FILE *Fichero);

struct sRegistro {
char Nombre[25];
int Edad;
float Sueldo;
} registro;

int main()
{
int opcion;
int exit = 0;
FILE *fichero;

while (!exit)
{
menu();
printf("\nOpcion: ");
scanf("%d", &opcion);

switch(opcion)
{
case 1:
CrearFichero(fichero);
break;
case 2:
InsertarDatos(fichero);
break;
case 3:
VerDatos(fichero);
break;
case 4:
exit = 1;
break;
default:
printf("\nopcion no valida");
}
}

return 0;
}

void menu()
{
Manejo de archivos 11

printf("\nMenu:");
printf("\n\t1. Crear fichero");
printf("\n\t2. Insertar datos");
printf("\n\t3. Ver datos");
printf("\n\t4. Salir");
}

void CrearFichero(FILE *Fichero)


{
Fichero = fopen("fichero", "r");

if(!Fichero)
{
Fichero = fopen("fichero", "w");
printf("\nArchivo creado!");
}
else
{
printf("\nEl fichero ya existe!");
}

fclose (Fichero);

return;
}

void InsertarDatos(FILE *Fichero)


{
Fichero = fopen("fichero", "a+");

if(Fichero == NULL)
{
printf("\nFichero no existe! \nPor favor creelo");
return;
}

printf("\nDigita el nombre: ");


scanf("%s", registro.Nombre);

printf("\nDigita la edad: ");


scanf("%d", &registro.Edad);

printf("\nDigita el sueldo: ");


scanf("%f", &registro.Sueldo);

fwrite(&registro, sizeof(struct sRegistro), 1, Fichero);


Manejo de archivos 12

fclose(Fichero);

return;
}

void VerDatos(FILE *Fichero)


{
int numero = 1;

Fichero = fopen("fichero", "r");

if(Fichero == NULL)
{
printf("\nFichero no existe! \nPor favor creelo");
return;
}

fread(&registro, sizeof(struct sRegistro), 1, Fichero);

printf("\nNumero \tNombre \tEdad \tSueldo");

while(!feof(Fichero))
{
printf("\n%d \t%s \t%d \t%.2f", numero, registro.Nombre,
registro.Edad, registro.Sueldo);
fread(&registro, sizeof(struct sRegistro), 1, Fichero);
numero++;
}

fclose(Fichero);

return;
}

fprintf
La función fprintf funciona igual que printf en cuanto a parámetros, pero la salida se dirige a un archivo en lugar de
a la pantalla.
El prototipo correspondiente de fprintf es:

int fprintf(FILE *archivo, const char *formato, argumento, ...);

Podemos ver un ejemplo de su uso, abrimos el documento "fichero.txt" en modo lectura/escritura y escribimos
dentro de el.

#include <stdio.h>

int main ( int argc, char **argv )


{
Manejo de archivos 13

FILE *fp;

char buffer[100] = "Esto es un texto dentro del fichero.";

fp = fopen ( "fichero.txt", "r+" );

fprintf(fp, buffer);
fprintf(fp, "%s", "\nEsto es otro texto dentro del fichero.");

fclose ( fp );

return 0;
}

Estructuras y Uniones
En la creacion de soluciones para algunos problemas surge la necesidad de agrupar datos de diferente tipo o de
manejar datos que serian muy dificil de describir en los tipos de datos primitivos, esta es la situacion en la que
debemos aprovecharnos de las caracteristicas que hacen al lenguaje C especial, o sea el uso de estructuras uniones y
punteros.

Estructuras
Una estructura contiene varios datos. La forma de definir una estructura es haciendo uso de la palabra clave struct.
Aqui hay ejemplo de la declaracion de una estructura:

struct mystruct
{
int int_member;
double double_member;
char string_member[25];
} variable;

"variable" es una instancia de "mystruct" y no es necesario ponerla aquí. Se podria omitir de la declaracion de
"mystruct" y más tarde declararla usando:

struct mystruct variable;

También es una práctica muy común asignarle un alias o sinónimo al nombre de la estructura, para evitar el tener que
poner "struct mystruct" cada vez. C nos permite la posibilidad de hacer esto usando la palabra clave typedef, lo que
crea un alias a un tipo:

typedef struct
{
...
} Mystruct;

La estructura misma no tiene nombre (por la ausencia de nombre en la primera linea), pero tiene de alias "Mystruct".
Entonces se puede usar así:
Estructuras y Uniones 14

Mystruct variable;

Note que es una convencion, y una buena costumbre usar mayúscula en la primera letra de un sinónimo de tipo. De
todos modo lo importante es darle algún identificador para poder hacer referencia a la estructura: podriamos tener
una estructura de datos recursiva de algún tipo.
Ejemplo de una estructura :

/*
* estructura.c
*
* Julio César Brizuela <[email protected]> 2009
*
* para el wikilibro "Programación en C (fundamentos)"
* bajo licencia FDL, adaptado del Dominio Público
*
* Nombre Miembro Tipo
* Titulo char[30]
* Artista char[25]
* Precio float
* Total Canciones int
*/

#include <stdio.h>
#include <string.h>

/* definimos una estructura para cds */


struct cd
{
char titulo[30];
char artista[25];
float precio;
int canciones;
} Cd1 = { /* inicializamos la estructura Cd1 creaa con sus valores
* usando las definiciones iniciales*/
"Canciones Bebe", /* titulo */
"Pinocho", /* artista */
12.50, /* precio */
16 /* total canciones */
};

int main(void)
{
struct cd Cd2; /* definimos una nueva estructura llamado cd2 */

/* asignamos valores a los tipos de datos del cd2 */


strcpy(Cd2.titulo, "New Age");
Estructuras y Uniones 15

/* la forma de insertar valores a un


* tipo char en una estructura es usando strcpy
* de la libreria string.h
*/
strcpy(Cd2.artista, "Old Man");
Cd2.precio = 15.00;
Cd2.canciones = 12;

/* la forma de acceder a los valores de una estructura */


/* es usando el "." despues de la definicion del dato*/
printf("\n Cd 1");
printf("\n Titulo: %s ", Cd1.titulo);
printf("\n Artista: %s ", Cd1.artista);
printf("\n Total Canciones: %d ", Cd1.canciones);
printf("\n Precio Cd: %f ", Cd1.precio);

printf("\n");
printf("\n Cd 2");
printf("\n Titulo: %s ", Cd2.titulo);
printf("\n Artista: %s ", Cd2.artista);
printf("\n Total Canciones: %d ", Cd2.canciones);
printf("\n Precio Cd: %.2f ", Cd2.precio); /* el .2 que esta
entre %f
* sirve para mostrar
unicamente
* 2 decimales
despues del punto*/

return 0;
}

Estructuras Anidadas
Una estructura puede estar dentro de otra estructura a esto se le conoce como anidamiento o estructuras anidadas. Ya
que se trabajan con datos en estructuras si definimos un tipo de dato en una estructura y necesitamos definir ese dato
dentro de otra estructura solamente se llama el dato de la estructura anterior.
Definamos una estructura en nuestro programa:

struct empleado /* creamos una estructura llamado empleado*/


{
char nombre_empleado[25];
char direccion[25];
char ciudad[20];
char provincia[20];
long int codigo_postal;
double salario;
}; /* las estructuras necesitan punto y coma (;) al final */

Y luego necesitamos una nueva estructura en nuestro programa:


Estructuras y Uniones 16

struct cliente /* creamos una estructura llamada cliente */


{
char nombre_cliente[25];
char direccion[25];
char ciudad[20];
char provincia[20];
long int codigo_postal;
double saldo;
}; /* las estructuras necesitan punto y coma (;) al final */

Podemos ver que tenemos datos muy similares en nuestras estructuras, asi que podemos crear una sola estructura
llamada infopersona con estos datos idénticos:

struct infopersona /* creamos la estructura que contiene datos


parecidos */
{
char direccion[25];
char ciudad[20];
char provincia[20];
long int codigo_postal;
}; /* las estructuras necesitan punto y coma (;) al final */

Y crear las nuevas estructuras anteriores, anidando la estructura necesaria:

struct empleado /* se crea nuevamente la estructura */


{
char nombre_empleado[25];
/* creamos direcc_empleado con "struct" del tipo "estructura
infopersona" */
struct infopersona direcc_empleado;
double salario;
}; /* las estructuras necesitan punto y coma (;) al final */

<source lang=c>
struct cliente /* se crea nuevamente la estructura */
{
char nombre_cliente[25];
/* creamos direcc_cliente con "struct" del tipo "estructura
infopersona" */
struct infopersona direcc_cliente;
double saldo;
}; /* las estructuras necesitan punto y coma (;) al final */

Y acá el ejemplo completo con estructuras anidadas:

/*
* estructura2.c
*
* Julio César Brizuela <[email protected]> 2009
*
Estructuras y Uniones 17

* para el wikilibro "Programación en C (fundamentos)"


* bajo licencia FDL, adaptado del Dominio Público
*
* Nombre Miembro Tipo
*
* Titulo char[30]
* Artista char[25]
* Precio float
* Total Canciones int
*/

#include <stdio.h>
#include <string.h>

/* creamos nuestra estructura con datos similares */


struct infopersona
{
char direccion[25];
char ciudad[20];
char provincia[20];
long int codigo_postal;
}; /* las estructuras necesitan punto y coma (;) al final */

/* creamos nuestra estructura empleado */


struct empleado
{
char nombre_empleado[25];
/* agregamos la estructura infopersona
* con nombre direcc_empleado
*/
struct infopersona direcc_empleado;
double salario;
}; /* las estructuras necesitan punto y coma (;) al final */

/* creamos nuestra estructura cliente */


struct cliente
{
char nombre_cliente[25];
/* agregamos la estructura infopersona
* con nombre direcc_cliente
*/
struct infopersona direcc_cliente;
double saldo;
}; /* las estructuras necesitan punto y coma (;) al final */

int main(void)
{
Estructuras y Uniones 18

/* creamos un nuevo cliente */


struct cliente MiCliente;

/*inicializamos un par de datos de Micliente */


strcpy(MiCliente.nombre_cliente,"Jose Antonio");
strcpy(MiCliente.direcc_cliente.direccion, "Altos del Cielo");
/* notese que se agrega direcc_cliente haciendo referencia
* a la estructura infopersona por el dato direccion
*/

/* imprimimos los datos */


printf("\n Cliente: ");
printf("\n Nombre: %s", MiCliente.nombre_cliente);
/* notese la forma de hacer referencia al dato */
printf("\n Direccion: %s", MiCliente.direcc_cliente.direccion);

/* creamos un nuevo empleado */


struct empleado MiEmpleado;

/*inicializamos un par de datos de MiEmplado */


strcpy(MiEmpleado.nombre_empleado,"Miguel Angel");
strcpy(MiEmpleado.direcc_empleado.ciudad,"Madrid");
/* para hacer referencia a ciudad de la estructura infopersona
* utilizamos direcc_empleado que es una estructura anidada
*/

/* imprimimos los datos */


printf("\n");
printf("\n Empleado: ");
printf("\n Nombre: %s", MiEmpleado.nombre_empleado);
/* notese la forma de hacer referencia al dato */
printf("\n Ciudad: %s", MiEmpleado.direcc_empleado.ciudad);

return 0;
}

Uniones
La definicion de "union" es similar a la de "estructura", La diferencia entre las dos es que en una estructura, los
miembros ocupan diferentes areas de la memoria, pero en una union, los miembros ocupan la misma area de
memoria. Entonces como ejemplo:

union {
int i;
double d;
} u;

El programador puede acceder a través de "u.i" o de "u.d", pero no de ambos al mismo tiempo. Como "u.i" y "u.d"
ocupan la misma área de memoria, modificar uno modifica el valor del otro, algunas veces de maneras
Estructuras y Uniones 19

impredecibles.
El tamaño de una union es el de su miembro de mayor tamaño.
Ejemplo de una unión:

/*
* uniones.c
*
* Julio César Brizuela <[email protected]> 2009
*
* para el wikilibro "Programación en C (fundamentos)"
* bajo licencia FDL, adaptado del Dominio Público
*/

#include <stdio.h>
#include <string.h>

/*Creamos una union*/


union frases
{
char mensajes[50];
char ayudas[50];
char lineas[50];
} palabra;

/*Creamos una estructura*/


struct comparte
{
char mensajes[50];
char ayudas[50];
char lineas[50];
}Sistema;

/*Notece que la estructura y la union tienen los mismos tipos de datos*/

int main(int argc, char** argv)


{
/*Inicializamos*/
strcpy(palabra.mensajes, "Primer Mensaje");

/*Inicializamos*/
strcpy(palabra.ayudas, "Una Ayuda");

printf("\nFrases en Union: ");

/*Imprimimos mensajes de union*/


printf("\n1- %s", palabra.mensajes);
Estructuras y Uniones 20

/*Imprimimos ayudas de union*/


printf("\n2- %s", palabra.ayudas);

/*Inicializamos*/
strcpy(Sistema.mensajes, "Primer Mensaje");

/*Inicializamos*/
strcpy(Sistema.ayudas, "Una Ayuda");

/* Podemos notar que aunque inicializamos los valores


* al imprimir se tiene el mismo valor para cada miembro
* de la estructura, esto se debe a que las uniones usan el
* mismo espacio de memoria para todos los elementos
* de la union, siendo del tamaño de su miembro de
* mayor tamaño, en este caso 50 bytes.
* Entonces los tres miembros creados dentro de la
* union comparten esos 50 bytes.
* Entonces el ultimo valor agregado a la union es
* el que se tiene.
*/

printf("\n\nFrases en Struct: ");

/*Imprimimos mensajes de struct*/


printf("\n1- %s", Sistema.mensajes);

/*Imprimimos ayudas de union*/


printf("\n2- %s", Sistema.ayudas);

/* En la estructura comparte, se reservan 150 bytes


* de memoria para los tres miembros, en este caso
* cada uno es independiente en memoria, asi pues se
* puede inicializar cada uno o usar como un campo
* independiente.
*/

return 0;
}
Estructuras y Uniones 21

Enumeraciones
Una enumeracion (enum) es un tipo definido con constante de tipo entero. En la declaracion de un tipo enum
creamos una lista de tipo de datos que se asocian con las constantes enteras 0, 1, 2, 3, 4, 5...
su forma de definirlas es la siguiente:

enum
{
enumerador1, enumerador2, … enumeradorn
};

enum Nombre
{
enumerador1, enumerador2, … enumeradorn
};

En este caso al ser declaradas enumerador1 toma el valor entero de 0, enumerador2 el valor de 1 y asi sucesivamente
para cada una de las expresiones siguientes.
Al declarar la enum se puede asociar a los tipos de datos a valores constantes en vez de la asociacion que por defecto
se realiza (0, 1, 2, …), se utiliza entonces este formato:

enum Nombre
{
enumerador1 = valor_constante1,
enumerador2 = valor_constante2,
...
enumeradorn = valor_constanten,
};

Un ejemplo de una enum:

enum Boolean
{
FALSE,
TRUE
};

Se definen dos constantes para las constantes true y false con valores iguales a 0 para False y 1 para True.
Ejemplo:

/*
* Enum.c
*
* Julio César Brizuela <[email protected]> 2009
*
* para el wikilibro "Programación en C (fundamentos)"
* bajo licencia FDL, adaptado del Dominio Público
*/

#include <stdio.h>
Estructuras y Uniones 22

enum Boolean
{
FALSE, TRUE
};
/* Se define un enum para emular las constantes
* True y False con valores de 0 y 1.
* Notese que las enum no necesitan ; al final
* de cada tipo de dato.
*/

/* Definimos una funcion del tipo enum llamada numero*/


enum Boolean numero(char c);

int main(int argc, char** argv)


{
char caracter;
int Numeros = 0;

printf("\nIntroduce un texto. Para terminar: Enter. \n\t");

/* Tenemos un while que mientras no se presione Enter


* seguira leyendo un tipo de dato caracter
*/
while((caracter = getchar()) != '\n')
{
if (numero(caracter))
{
Numeros++;
}
}
printf("\nTotal de Numeros leidos: %d", Numeros);

return 0;
}

enum Boolean numero(char c)


{
switch(c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
Estructuras y Uniones 23

case '8':
case '9':
return TRUE;
/* Mientras el caracter valga de 0 a 9 retornara TRUE (1) */
default:
return FALSE;
/* Por default retornara FALSE (0) */
}
}

En la siguiente enum se declaran las variables inicializando la primera y las demas con los siguientes valores enteros:

/*
* Enum2.c
*
* Julio César Brizuela <[email protected]> 2009
*
* para el wikilibro "Programación en C (fundamentos)"
* bajo licencia FDL, adaptado del Dominio Público
*/

#include <stdio.h>

enum DiasSemanas
{
Domingo = 2,
Lunes,
Marte,
Miercoles,
Jueves,
Viernes,
Sabado
};
/* Podemos inicializar nuestra primer constante Domingo
* en 2, asi pues las demas los siguientes valores enteros.
*/

int main(int argc, char** argv)


{

enum DiasSemanas dia;

for (dia = Domingo; dia <= Sabado; dia++)


{
printf("%d ", dia); /* Salida: 2 3 4 5 6 7 8 */
}

return 0;
Estructuras y Uniones 24

A los enumeradores se pueden asignar valores o expresiones constantes durante la declaracion:

enum Hexaedro
{
VERTICE = 8,
LADOS = 12,
CARAS = 6
};

Punteros
Un puntero es una variable que contiene la dirección, o ubicación en memoria, de algún valor.

Declarando puntero
Tenga en cuenta el siguiente bloque de código que declara 2 punteros

/*1*/ struct MyStruct {


/*2*/ int m_aNumber;
/*3*/ float num2;
/*4*/ };
/*5*/
/*6*/ int * pJ2;
/*7*/ struct MyStruct * pAnItem;

Las primeras 4 líneas definen la estructura. La linea 6 declara una variable que apuntará a un entero, y la línea 7
declara una variable que apunta a algo de la estructura MyStruct. Entonces declarar un puntero es algo que apunta a
algo de algún tipo, más que contener el tipo. El asterisco (*) se coloca antes del nombre de la variable.
En las siguientes líneas de código, var1 es un puntero a un entero largo (long) mientras var2 es un entero largo
(long) y no un puntero a un entero largo. En la segunda línea se declara p3 como un puntero a un puntero de un
entero.

long * var1, var2;


int ** p3;

Los punteros se usan habitualmente como parámetros de funciones. El siguiente código muestra como declarar una
función que usa un puntero como argumento. Teniendo en cuenta que C pasa todos los argumentos por valor, para
poder modificar un valor desde el código de la función, se debe usar un puntero al valor a modificar. También se
usan punteros a estructuras como argumentos de una función aún cuando la estructura no sea modificada dentro de la
función. Esto es realizado para evitar copiar todo el contenido de la estructura dentro de la pila.

int MyFunction( struct MyStruct *pStruct );


Punteros 25

Asignando valores a punteros


Continuamos con el proceso de asignar valores a los punteros, para esto utilizamos el operador & o 'la dirección de'.

int myInt;
int *pPointer;
struct MyStruct dvorak;
struct MyStruct *pKeyboard;

pPointer = &myInt;
pKeyboard = &dvorak;

Aquí, pPointer apuntara a myInt y pKeyboard apuntara a dvorak.


Los punteros también pueden ser asignados a referencias de memorias dinamicas creadas comúnmente por las
funciones malloc() y calloc().

#include <stdlib.h>
...
struct MyStruct *pKeyboard;
...
pKeyboard = malloc(sizeof(struct MyStruct));
...

La función malloc retorna un puntero de memoria asignada de manera dinámica (o NULL si falla). El tamaño de esta
memoria es definido de modo que pueda contener la estructura MyStruct
El siguiente código es un ejemplo mostrando un puntero siendo asignado a una referencia y se retorna el valor del
puntero en la función.

static struct MyStruct val1, val2, val3, val4;


...
struct MyStruct *ASillyFunction( int b )
{
struct MyStruct *myReturn;

if (b == 1) myReturn = &val1;
else if (b==2) myReturn = &val2;
else if (b==3) myReturn = &val3;
else myReturn = &val4;

return myReturn;
}
...
struct MyStruct *strPointer;
int *c, *d;
int j;
...
c = &j; /* puntero asignado usando el
operador & */
d = c; /* asignando un puntero a otro
*/
Punteros 26

strPointer = ASillyFunction( 3 ); /* puntero retornado de la


funcion */

Cuando se retorna un puntero de una funcion, se tiene que tener cuidado de que el valor apuntado no sea de una
referencia de una variable local a la funcion, porque estos valores desaparecen despues de salir de la funcion y el
puntero estaria mal referenciado, causando errores en tiempo de ejecucion en el programa. La memoria creada
dinamicamente dentro de la funcion puede devolverse como puntero, el valor de retorno aunque es creado en una
variable local la direccion como tal se devuelve por valor dejando esta informacion valida.

Manejo dinámico de memoria


Esta sección no está lista si tienes algún conocimiento sobre este tema por favor, regístrate y contribuye con nuestro
libro.
Breve introducción.

Memoria dinámica
Es memoria que se reserva en tiempo de ejecución. Su tamaño puede variar durante la ejecución del programa y
puede ser liberado mediante la función free.

Memoria estática
Es el espacio de memoria que se crea al declarar variables, arrays o matrices de forma estática (ej. int x, char
c[MAX], etc) y cuyo tamaño no podemos modificar durante la ejecución del programa ni liberar el espacio que
ocupa.

Diferencias, ventajas y desventajas


La memoria reservada de forma dinámica suele estar alojada en el heap o almacenamiento libre, y la memoria
estática en el stack o pila (con excepción de los objetos de duración estática, que se verán más adelante, los cuales
suelen colocarse en una zona estática de datos). La pila suele ser una zona muy limitada. El heap, en cambio, en
principio podría estar limitado por la cantidad de memoria disponible durante la ejecución del programa y el máximo
de memoria que el sistema operativo permita direccionar a un proceso. La pila puede crecer de forma dinámica, pero
esto generalmente depende del sistema operativo. En cualquier caso, lo único que se puede asumir es que casi con
seguridad dispondremos de menor espacio en la pila que en el heap.
Otra ventaja de la memoria dinámica es que se puede ir incrementando durante la ejecución del programa. Esto
permite, por ejemplo, trabajar con arreglos dinámicos. Aunque en C, a partir del estándar C99 se permite la creación
de arreglos cuyo tamaño se determina en tiempor de ejecución, no todos los compiladores implementan este
estándar. Además, se sigue teniendo la limitante de que su tamaño no puede cambiar una vez que se especifica, cosa
que sí se puede lograr asignando memoria de forma dinámica.
Una desventaja de la memoria dinámica es que es más difícil de manejar. La memoria estática tiene una duración
fija, que se reserva y libera de forma automática. En contraste, la memoria dinámica se reserva de forma explícita y
continúa existiendo hasta que sea liberada, generalmente por parte del programador.
La memoria dinámica puede afectar el rendimiento. Puesto que con la memoria estática el tamaño de las variables se
conoce en tiempo de compilación, esta información está incluida en el código objeto generado. Cuando se reserva
memoria de manera dinámica, se tienen que llevar a cabo varias tareas, como buscar un bloque de memoria libre y
almacenar la posición y tamaño de la memoria asignada, de manera que pueda ser liberada más adelante. Todo esto
Manejo dinámico de memoria 27

representa una carga adicional, aunque esto depende de la implementación y hay técnicas para reducir su impacto.

El lenguaje C y el manejo de la memoria


Todos los objetos tienen un tiempo de vida, es decir, el tiempo durante el cual se garantiza que el objeto exista. En C,
existen 3 tipos de duración: estática, automática y asignada. Las variables globales y las variables locales declaradas
con el especificador static tienen duración estática. Se crean antes de que programa inicie su ejecución y se
destruyen cuando el programa termina. Las variables locales no static tienen duración automática. Se crean al
entrar al bloque en el que fueron declaradas y se destruyen al salir de ese bloque. Duración asignada se refiere a los
objetos cuya memoria se reserva de forma dinámica. Como se explicó anteriormente, esta memoria se crea y se debe
liberar de forma explícita. Los arreglos de longitud variable de C99 son un caso especial. Tienen duración
automática, con la particularidad de que son creados a partir de su declaración.
La bilbioteca estándar de C proporciona las funciones malloc, calloc, realloc y free para el manejo
de memoria dinámica. Estas funciones están definidas en el archivo de cabecera stdlib.h.

malloc
La función malloc reserva un bloque de memoria y devuelve un puntero void al inicio de la misma. Tiene la
siguiente definición:
void *malloc(size_t size);
donde el parámetro size especifica el número de bytes a reservar. En caso de que no se pueda realizar la
asignación, devuelve el valor nulo (definido en la macro NULL), lo que permite saber si hubo errores en la
asignación de memoria.
A continuación se muestra un ejemplo de su uso:

int *i;

/* Reservamos la memoria suficiente para almacenar un int y asignamos
su dirección a i */

i = malloc(sizeof(int));

/* Verificamos que la asignación se haya realizado correctamente */


if (i == NULL) {
/* Error al intentar reservar memoria */
}

Uno de los usos más comunes de la memoria dinámica es la creación de vectores cuyo número de elementos se
define en tiempo de ejecución:

int *vect1, n;
printf("Número de elementos del vector: ");
scanf("%d", &n);

/* Reservar memoria para almacenar n enteros */


vect1 = malloc(n * sizeof(int);

/* Verificamos que la asignación se haya realizado correctamente */


if (vect1 == NULL) {
Manejo dinámico de memoria 28

/* Error al intentar reservar memoria */


}

calloc
La función calloc funciona de modo similar a malloc, pero además de reservar memoria, inicializa a 0 la
memoria reservada. Se usa comúnmente para arreglos y matrices. Está definida de esta forma:
void *calloc(size_t nmemb, size_t size);
El parámetro nmemb indica el número de elementos a reservar, y size el tamaño de cada elemento. El ejemplo
anterior se podría reescribir con calloc de esta forma:

int *vect1, n;
printf("Número de elementos del vector: ");
scanf("%d", &n);

/* Reservar memoria para almacenar n enteros */


vect1 = calloc(n, sizeof(int));

/* Verificamos que la asignación se haya realizado correctamente */


if (vect1 == NULL) {
/* Error al intentar reservar memoria */
}

realloc
La función realloc redimensiona el espacio asignado de forma dinámica anteriormente a un puntero. Tiene la
siguiente definición:
void *realloc(void *ptr, size_t size);
Donde ptr es el puntero a redimensionar, y size el nuevo tamaño, en bytes, que tendrá. Si el puntero que se le
pasa tiene el valor nulo, esta función actúa como malloc. Si la reasignación no se pudo hacer con éxito, devuelve
un puntero nulo, dejando intacto el puntero que se pasa por parámetro. Al usar realloc, se debería usar un puntero
temporal. De lo contrario, podríamos tener una fuga de memoria, si es que ocurriera un error en realloc.
Ejemplo de realloc usando puntero temporal:

/* Reservamos 5 bytes */
void *ptr = malloc(5);

/* Redimensionamos el puntero (a 10 bytes) y lo asignamos a un puntero
temporal */
void *tmp_ptr = realloc(ptr, 10);

if (tmp_ptr == NULL) {
/* Error: tomar medidas necesarias */
}
else {
/* Reasignación exitosa. Asignar memoria a ptr */
ptr = tmp_ptr;
}
Manejo dinámico de memoria 29

Cuando se redimension la memoria con realloc, si el nuevo tamaño (parámetro size) es mayor que el anterior,
se conservan todos los valores originales, quedando los bytes restantes sin inicializar. Si el nuevo tamaño es menor,
se conservan los valores de los primeros size bytes. Los restantes también se dejan intactos, pero no son parte del
bloque regresado por la función.

free
La función free sirve para liberar memoria que se asignó dinámicamente. Si el puntero es nulo, free no hace
nada. Tiene la siguiente definición:
void free(void *ptr);
El parámetro ptr es el puntero a la memoria que se desea liberar:

int *i;
i = malloc(sizeof(int));

free(i);

Una vez liberada la memoria, si se quiere volver a utilizar el puntero, primero se debe reservar nueva memoria con
malloc o calloc:

int *i = malloc(sizeof(int));

free(i);

/* Reutilizamos i, ahora para reservar memoria para dos enteros */


i = malloc(2 * sizeof(int));

/* Volvemos a liberar la memoria cuando ya no la necesitamos */
free(i);

¿Que es un buffer?

Buenas prácticas
Como se vio en las secciones anteriores, siempre que se reserve memoria de forma dinámica con malloc,
realloc o calloc, se debe verificar que no haya habido errores (verificando que el puntero no sea NULL).
Cuando se trata de verificar el valor de un puntero (y sólo en ese caso), se puede usar de forma indistinta 0 ó NULL.
Usar uno u otro es cuestión de estilo. Como ya se vio, las funciones de asignación dinámica de memoria devuelven
un puntero void. Las reglas de C establecen que un puntero void se puede convertir automáticamente a un
puntero de cualquier otro tipo, por lo que no es necesario hacer una conversión (cast), como en el siguiente ejemplo:

/* El puntero void devuelto por malloc es convertido explícitamente a


puntero int */
int *i = (int *)malloc(sizeof(int));

Aunque no hay un consenso, muchos programadores prefieren omitir la conversión anterior porque la consideran
menos segura. Si accidentalmente se olvida incluir el archivo stdlib.h (donde están definidas malloc,
calloc, realloc y free) en un programa que use dichas funciones, el comportamiento puede quedar
indefinido. Si omitimos la conversión explícita, el compilador lanzará una advertencia. Si, en cambio, realizamos la
conversión, el compilador generará el código objeto de forma normal, ocultado el bug.
Manejo dinámico de memoria 30

Una posible razón para usar la conversión explícita es si se escribe código en C que se vaya a compilar junto con
código C++, ya que en C++ sí es necesario realizar esa conversión.
En cualquier caso, dado que el manejo de memoria es un tema complejo, y éste es un error muy común, se debe
hacer énfasis en que cuando se trabaja con memoria dinámica, siempre se debe verificar que se incluya el archivo
stdlib.h.
Tratar de utilizar un puntero cuyo bloque de memoria ha sido liberado con free puede ser sumamente peligroso.
El comportamiento del programa queda indefinido: puede terminar de forma inesperada, sobrescribir otros datos y
provocar problemas de seguridad. Liberar un puntero que ya ha sido liberado también es fuente de errores.
Para evitar estos problemas, se recomienda que después de liberar un puntero siempre se establezca su valor a NULL.

int *i;
i = malloc(sizeof(int));

free(i);
i = NULL;

El proceso de compilación
Normalmente, a la hora de programar no creamos un único archivo C .c, sino varios de ellos conteniendo diferentes
funciones del programa. Esto nos proporciona varias ventajas: una mejor organización del código, una mejor
modularidad y, sobre todo, más facilidad (y velocidad) a la hora de compilar.
El proceso de compilación que hemos tratado hasta ahora se divide en realidad en dos etapas, que el compilador nos
esconde en una: compilación propiamente dicha y enlazado. En la primera etapa, la de compilación, nuestro código
en C se transforma en código objeto, es decir, código máquina (instrucciones que el ordenador puede ejecutar) en
ficheros .o, mientras que en la segunda etapa (enlazado) estos ficheros objeto son unidos entre sí para formar el
fichero ejecutable (normalmente sin extensión en el mundo Unix, o con extensión .com o .exe en el mundo
MS-DOS/Windows).
De esta manera, si no hemos modificado el fichero .c que se compila a un determinado fichero .o podemos
ahorrarnos esa parte de la compilación cuando hagamos un cambio en otra parte del programa.
Además tenemos los archivos de cabecera .h, que utilizamos para definir parámetros que se utilizan en el código
(caso del fuente1.h del ejemplo) o para definir todas las funciones que vamos a utilizar en todos los archivos
.c (caso del cabeceras.h), ya que si recordamos, las funciones (como todo en C) deben definirse antes de
usarse, y es posible que estén en otro fichero, lo cual nunca es considerado por el compilador como "antes". Hay que
tener en cuenta que el fichero en el que se encuentra la función main() llama, necesariamente, a todos los demás
ficheros .c, directa o indirectamente, ya que de lo contrario lo que tendríamos en esos ficheros sería perfectamente
inútil.
Si típicamente compilaríamos el ejemplo con

$ gcc -o programa fuente1.c fuente2.c fuente3.c

para realizar esta compilación por pasos, éstos serían:

$ gcc -c fuente1.c #crea fuente1.o


$ gcc -c fuente2.c #crea fuente2.o
$ gcc -c fuente3.c #crea fuente3.o
$ gcc -o programa fuente1.o fuente2.o fuente3.o #crea programa
El proceso de compilación 31

ya que la opción -c del compilador lo que le dice es que detenga el proceso antes de enlazar, creando los ficheros
.o necesarios.

Los pasos del proceso


Lo primero que le ocurre a un fichero .c de código C es el preprocesado. En este paso se sustituyen todas las
macros y se eliminan los comentarios. El resultado, si lo vemos independientemente, es un fichero de código C
preprocesado, o .i.
El segundo paso es la compilación propiamente dicha, en el que el código C preprocesado se convierte en código
ensamblador, que si lo vemos independientemente es un fichero .s.
El tercer paso es el ensamblado del código ensamblador, lo que lo convierte en código máquina. Un fichero de
código máquina también es llamado fichero objeto, y su extensión típica es .o.
Dado que el camino anterior normalmente convierte un fichero en un fichero, se suele hacer en un sólo paso, de
código C a fichero objeto.
El último paso es el enlazado de los ficheros objeto en el ejecutable final.

Ejemplos
Tomemos el código C más simple posible:

int main(void) /*Ejemplo*/


{
return(0);
}

Al preprocesarlo tendremos:

# 1 "prueba.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "prueba.c"
int main(void)
{
return(0);
}

Vemos que el comentario ha desaparecido. En su lugar aparecen comentarios específicos del preprocesador. Al
compilarlo tenemos:

.file "prueba.c"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
El proceso de compilación 32

addl $15, %eax


shrl $4, %eax
sall $4, %eax
subl %eax, %esp
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.0.3 20051023 (prerelease) (Debian 4.0.2-3)"
.section .note.GNU-stack,"",@progbits

Un precioso código ensamblador que enseguida convertimos en un ilegible código máquina:

ELFØ4( UåäðžÀÀÁèÁà)ÄžÉÃGCC: (GNU) 4.0.3 20051023 (prerelease) (Debian 4.0.2-3)


.symtab.strtab.shstrtab.text.data.bss.comment.note.GNU-stack4#!XX,X95E@Àñÿ
#prueba.cmain

Ese código máquina es el fichero .o que normalmente obtenemos directamente del código C. Finalmente, ese
código objeto es enlazado con las librerías necesarias para formar un ejecutable:

ìÀ£žÿÒ¡žÒuëÆŒÉÃöUå¡ÌÀtžÀtäðPTRhhQVhhè³ÿÿÿôUåSQè[ßüÿÿÿÒtè
ÿÿÿX[ÉÃUå=Œt+ën_used__libc_start_mainGLIBC_2.0$ii
hÌÿÐÄÉÃUåäðžÀÀÁèÁà)ÄžÉÃUåWVSì
è[Ãþè²þÿÿ ÿÿÿ ÿÿÿ)ÐÁøEðuÄ
[^_]ÃŽ&1ÿÖ¶¿ÿGÆ;}ðrõÄ
[^_]öŒ'UåWVSì

[Ã ÿÿÿ ÿÿÿ)ÐÁøEðHøÿt41ÿ¶¿ÿGî9}ðuõèDÄ

[^_]ÃUåSR¡Œøÿt»Œ¶¿ÿÐCüëøÿuóX[]ÃUåSPè[ÃþèVþÿÿX[ÉÃÿÿÿÿÿÿÿÿ$
HÀp \
Y
þÿÿo$ÿÿÿoðÿÿoÐÈGCC: (GNU) 4.0.2 (Debian 4.0.2-2)GCC: (GNU) 4.0.2
(Debian 4.0.2-2)GCC: (GNU) 4.0.3 20051023 (prerelease)
(Debian 4.0.2-3)GCC: (GNU) 4.0.3 20051023 (prerelease) (Debian
4.0.2-3)GCC: (GNU) 4.0.2 (Debian 4.0.2-2)GCC:
(GNU) 4.0.3 20051023 (prerelease) (Debian 4.0.2-3)GCC: (GNU) 4.0.2
(Debian 4.0.2-2)°",\

Ô$$̪q!y_IO_stdin_used°Ò../sysdeps/i386/elf/start.S/
space/debian/glibc/build-area/glibc-2.3.5/
build-tree/glibc-2.3.5/csuGNU AS
2.16.1XÔÔ¬F}xgMint\}nŽOV|/space/debian/glibc/build-area/glibc-2.3.5/
build-tree/i386-libc/csu/crti.S/space/debian/glibc/build-area/glibc-2.3.5/build-tree/glib
csuGNU AS 2.16.1f(/space/debian/glibc/build-area/glibc-2.3.5/
El proceso de compilación 33

build-tree/i386-libc/csu/crtn.S/space/debian/glibc/build-area/
glibc-2.3.5/build-tree/glibc-2.3.5/csuGNU AS 2.16.1%

>

>

4:

I?

T/û
../sysdeps/i386/elfstart.S°À01:"VWYX û
init.cš^û
/space/debian/glibc/build-area/glibc-2.3.5/build-tree/i386-libc/csucrti.S3,W\#,:Ô
,Wdd,,W^û
/space/debian/glibc/build-area/glibc-2.3.5/build-tree/i386-libc/csucrtn.Sªq
/space/debian/glibc
/build-area/glibc-2.3.5/build-tree/glibc-2.3.5/csuinit.cshort intlong
long intunsigned charlong
long unsigned intshort unsigned int_IO_stdin_usedGNU C 4.0.2 (Debian
4.0.2-2)
.symtab.strtab.shstrtab.interp.note.ABI-tag.hash.dynsym.dynstr.gnu.version.
gnu.version_r.rel.dyn.rel.plt.init.text.fini.rodata.eh_
frame.ctors.dtors.jcr.dynamic.got.got.plt.data.bss.comment.
debug_aranges.debug_pubnames.debug_
info.debug_abbrev.debug_line.debug_str#(( 1HH(7

ppP?ÀÀYGÿÿÿo
Tþÿÿo$$ c lD LL
El proceso de compilación 34

u\\ptt0{°°ä°žžŒ ħÌ̬ÐеºÃ°°
ŒŒÎŒ7×øæp%ö}
v
²0:
'|`!5 Üî(HpÀ$L \
t
°
°žŒÄÌаŒ ŒÄ-Ì: f@
rÀȞ̊`
ŒÐÅŒñÿÖŒñÿéŒñÿúŒñÿ#°*Ž7X
G\

Tc
dŒñÿph#
£ŒñÿªÀñÿ¯ŽŸ°Ë ß call_gmon_start__CTOR_LIST____DTOR_LIST____JCR_LIST__
completed.4463p.4462__do_global_dtors_auxframe_dummy__CTOR_
END____DTOR_END____FRAME_END____JCR_END____do_global_ctors_aux_DYNAMIC__
fini_array_end__fini_array_start__init_array_
end_GLOBAL_OFFSET_TABLE___init_array_start_fp_hw__dso_handle__libc_csu_fini_init_start__l
__libc_start_main@@GLIBC_2.0data_start_fini_edata_end_IO_stdin_used__data_start_Jv_Regist

Ejemplos
El "Hola Mundo"
#include <stdio.h>

int main (int argc,char **argv)


{
printf("Hola mundo\n");
return 0;
}

Nota: este programa está tomado del Dominio Público

$ ./holamundo
Hola Mundo
$

El "Hola Mundo" comentado


[Contexto]

/* Inclusión de archivos */
#include <stdio.h>

/* Función principal */
int main (int argc,char **argv)
Ejemplos 35

{
/* Impresión por pantalla y salida del programa*/
printf("Hola mundo\n");
return 0;
}

$ ./holamundoc
Hola Mundo
$

El "Hola Mundo" estructurado en funciones


[Contexto]
/*
holamundo.c
(c) Envite, 2004
para el wikilibro "Programación en C (fundamentos)"
bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h> /*Necesario para la función printf()*/
void holamundo(void) /*Función donde se ejecuta la lógica del programa*/
{
printf("Hola Mundo\n"); /*imprime la cadena*/
return; /*sale de la función*/
}
int main(void) /*Función principal del programa*/
{
holamundo(); /*llamada a la función que lleva el peso*/
return(0); /*sale del programa: correcto*/
}

$ ./holamundof
Hola mundo
$

Ejemplo de cálculo con enteros


[Contexto]
/*
ejemplo.c
(c) Envite, 2004
para el wikilibro "Programación en C (fundamentos)"
bajo licencia FDL
*/
#include <stdio.h> /*Necesario para la función printf()*/
int main(void) /*Función principal del programa*/
{
char resultado; /*Variable de tipo carácter donde se almacenará el resultado
de
Ejemplos 36

las operaciones.*/
resultado=5+2; /*Realizamos una suma.*/
printf("Resultado de la suma: %i\n",resultado);
resultado=5-2; /*Realizamos una resta.*/
printf("Resultado de la resta:%i\n",resultado);
resultado=5*2; /*Realizamos una multiplicación.*/
printf("Resultado de la multiplicación: %i\n",resultado);
resultado=5/2; /*Realizamos una división entera.*/
printf("Resultado de la división:%i\n",resultado);
return(0); /*Salimos del programa con el código 0 porque no ha habido
errores.*/
}

$ ./ejemploc
Resultado de la suma: 7
Resultado de la resta: 3
Resultado de la multiplicación: 10
Resultado de la división: 2
$

Control de acceso
[Volver al índice general] [Arriba]
[Contexto]
#include <stdio.h>
void acceso(unsigned char edad)
{
if (edad < 18)
 printf("No puedes acceder.\n");
else
 printf("Bienvenido.\n");
return;
}
int main(void)
{
acceso(18);
acceso(17);
return(0);
}

$ ./acceso
Bienvenido.
No puedes acceder.
$

[Volver al índice general] [Anterior: Salida por pantalla: printf()] [Arriba]


--Envite 01:31 11 dic, 2004 (UTC)
Glib 37

Glib
Introducción
El lenguaje C fué creado como un lenguaje multiplataforma capaz de sustituir al ensamblador y generar código
portable. Sin embargo a lo largo de los años se empiezan a hacer visibles algunas lagunas del diseño original.
Algunos ejemplos que pueden citarse:
• No existe estandarización en torno a la longitud real (en bits) de un entero. Un tipo int puede variar entre 16, 32 y
64 bits de longitud en función del sistema operativo y compilador utilizado.
• No existe un soporte estándar en el lenguaje para programación orientada a objetos.
• No existe un soporte estándar para tipos de estructuras de datos frecuentemente utilizadas como listas enlazadas,
tablas hash (también llamadas diccionarios).
• No existe un soporte estándar para textos Unicode (el estándar Unicode es posterior a la creación del C).
• No existe un soporte estándar para patrones de diseño frecuentemente utilizados como el "bucle principal de
eventos".
• No existe un API común para manejar aplicaciones multihilo como puede existir en Java y otros
lenguajes/plataformas.
La librería glib se creó con el fín de solucionar estas y otras lagunas originales del lenguaje proporcionando una capa
de compatibilidad real multiplataforma en sistemas tipo UNIX (Linux, Solaris, AIX, BSD, ...), Windows, OS/2 y
BeOS.
La librería glib es gratuita y licenciada bajo LGPL (Lesser GPL), lo cual significa en la práctica que puede ser
utilizada tanto en programas de código abierto como cerrado.
glib provee soporte para expresiones regulares tipo PERL, un conjunto de datos tipados más seguros que el C
estándar, soporte multihilo multiplataforma, un sistema de "bucle principal de eventos", colas asíncronas, carga
dinámica de módulos, soporte portable para uso de ficheros, tuberías y sockets, programación orientada a objetos,
utilidades de todo tipo para manipular textos, escaneado sintáctico, "Timers" y multitud de estructuras de datos
frecuentemente utilizadas "slices" de memoria, listas doblemente enlazadas, colas, secuencias (listas escalables),
tablas hash para búsqueda mediante llaves (donde la llave puede ser un texto o un objeto complejo), arrays
dinámicos, árboles binarios balanceados, árboles "N-ários" (árboles con N ramas por nodo), quarks, listas de datos
indexadas (accesibles mediante un identificador GQuark, relaciones y tuplas que pueden ser indexadas mediante un
número arbitrario de campos, caches para compartición de estructuras complejas de datos.
glib provee además un marco base de utilidades para realizar tests de funcionamiento en tiempo de desarrollo.
glib no provee soporte gráfico. Frecuentemente suele emparejarse con la librería gráfica GTK+ ya que la misma
utiliza glib internamente como soporte base. Sin embargo glib puede ser utilizado para aplicaciones embebidas o
servidor sin dependencia alguna del sistema gráfico.

Ejemplos de uso
glib se encuentra ampliamente documentado por los propios desarrolladores de la librería y existen tutoriales
disponibles en internet. Aquí se expondrá un ejemplo de código real dónde se utiliza glib para facilitar el desarrollo
en C:
Este código se utilizó para monitorizar un sistema Linux donde el disco duro había sido sustituido por un sistema de
ficheros en red. El código se encarga de crear un "perro vigia" encargado de resetear el sistema en caso de detectar
un fallo en el sistema de ficheros NFS (Net File System) del cual depende la máquina para su correcto
funcionamiento. El mismo sirve para comprobar la facilidad con que glib permite crear timers, callbacks invocadas
por el bucle principal de eventos o gestionar acceso de entrada/salida de forma más ordenada que las librerías
Glib 38

estándar de C:
#include <glib.h> /* glib */
GError* err = NULL;

gboolean isNFSUpAndRunning() {
/* Salvo que se indique lo contrario devuelve "KO"(0)
* Superados todos los test devolver'a "OK" (1)
*/
gboolean result = 0;
GIOChannel *gioChan = g_io_channel_new_file("/NFSFlag", "r", &err);
/*|*/
/*|*/startBlock1: {
/*|*/
if(NULL != err && g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
goto endBlock1;
}
g_io_channel_set_encoding(gioChan, NULL, NULL);
gsize *len = 0;
char* ret;
if(G_IO_STATUS_ERROR == g_io_channel_read_to_end(gioChan, &ret, len, &err)) {
goto endBlock1;
}
printf("%s", ret);
result = 1; // Todo es correcto. Devolvemos "OK"
/*|*/
/*|*/} endBlock1: {
/*|*/
if(NULL != err) { g_error_free(err); err = NULL; }
if(NULL != gioChan) g_io_channel_unref(gioChan);
/*|*/
/*|*/}
/*|*/
return result;
}

void hardReset() {
// http:/ / en. wikipedia. org/ wiki/ Magic_SysRq_key
GIOChannel *gioChan = g_io_channel_new_file("/proc/sysrq-trigger", "w", &err);
gsize bytes_written;
g_io_channel_write_chars(gioChan, "b", -1, &bytes_written, &err);
if (NULL != err) {
printf("debug:hardReset Se detect'o un error\n"); fflush(stdout);
}
g_io_channel_shutdown (gioChan, /*bFlush*/ TRUE, &err);
}

static gboolean callBackCheckNFS(gpointer user_data){


Glib 39

if (! isNFSUpAndRunning() ){
hardReset();
}
return TRUE;
}

static gboolean setTimeOut(GMainContext* ctx) {


g_return_if_fail (ctx != NULL);
// Creamos un objeto timer. source indica que es la fuente de eventos
GSource* source = g_timeout_source_new (60*1000 /*milisecs*/);
// Asociamos un callBack a la fuente de eventos (timer).
g_source_set_callback (source, callBackCheckNFS, (gpointer) ctx, NULL);
// Finalmente asociamos la fuente al contexto (bucle principal de eventos)
g_source_attach (source, ctx /*ctx->g_main_ctx*/);
g_source_unref (source);
return TRUE;
}

int main(int argc, char *argv[]) {


// Creamos el objeto bucle principal de eventos.
GMainLoop *loop = g_main_loop_new (NULL, FALSE);

/*
* Asociamos un Timer al bucle principal. Si falla algo abortamos el programa
* pues no tiene sentido continuar en tal caso.
*/
if (! setTimeOut(g_main_loop_get_context(loop)) ) {
return 1;
}
// Ejecutamos el bucle principal de eventos.
g_main_run (loop);
return 0;
}

Referencias
• El Interfaz de Programación de Aplicaciones (API) puede consultarse (en inglés) en la siguiente URL:
http://library.gnome.org/devel/glib/stable/
• El tutorial de la librería gráfica GTK+ (http://library.gnome.org/devel/gtk-tutorial/stable/) incluye también
ejemplos de glib y herramientas para compilar y linkar contra estas dos librerías.
• El exitoso escritorio para UNIX de código abierto GNOME (GNU Network Model Environment) así como la
plataforma Moblin de Intel, Nokia Maemo, Google Android y Google Chrome, la versión UNIX de Firefox y
otros programas de código abierto como "The Gimp", Inkscape o DIA, etc ... hacen un uso extenso de glib. El
código está disponible gratuitamente en la web y puede servir como una guía y referencia para utilizar glib en
proyectos de software complejos con millones de líneas de código y que son utilizados diariamente por millones
de personas.
Fuentes y contribuyentes del artículo 40

Fuentes y contribuyentes del artículo


Cadenas de caracteres  Fuente: http://es.wikibooks.org/w/index.php?oldid=123030  Contribuyentes: Maxy

Manejo de archivos  Fuente: http://es.wikibooks.org/w/index.php?oldid=171739  Contribuyentes: Exe, Gargo, Irtusb, Jpag87a, Malonph, ManuelGR, Mchingotto, Morza, Oleinad, Ruy Pugliesi,
Savh, Unopresente, Zerohours, 58 ediciones anónimas

Estructuras y Uniones  Fuente: http://es.wikibooks.org/w/index.php?oldid=172631  Contribuyentes: Gargo, Savh, Taichi, Zerohours, 8 ediciones anónimas

Punteros  Fuente: http://es.wikibooks.org/w/index.php?oldid=143224  Contribuyentes: Jcongote, Luckas Blade, 8 ediciones anónimas

Manejo dinámico de memoria  Fuente: http://es.wikibooks.org/w/index.php?oldid=166700  Contribuyentes: CaStarCo, Gargo, Url999, 2 ediciones anónimas

El proceso de compilación  Fuente: http://es.wikibooks.org/w/index.php?oldid=142589  Contribuyentes: Envite, 7 ediciones anónimas

Ejemplos  Fuente: http://es.wikibooks.org/w/index.php?oldid=171298  Contribuyentes: Envite, ManuelGR, Margamanterola, 9 ediciones anónimas

Glib  Fuente: http://es.wikibooks.org/w/index.php?oldid=141034  Contribuyentes: Earizon, 2 ediciones anónimas


Fuentes de imagen, Licencias y contribuyentes 41

Licencia
Creative Commons Attribution-Share Alike 3.0 Unported
//creativecommons.org/licenses/by-sa/3.0/

También podría gustarte