UC3M

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2017 - Enero 2018

Capítulo 1. Estructura de un programa en C

En la actualidad existen múltiples lenguajes de programación denominados de alto nivel, es decir, aquellos que permiten escribir programas en un formato lejano al lenguaje máquina o ensamblador que es el que utiliza el procesador para ejecutar todos los programas. Java es un lenguaje de alto nivel orientado a objeto pues los programas se estructuran alrededor de clases y objetos. C, en cambio, no dispone de objetos y se estructura en base a un conjunto de funciones, por lo que se denomina un lenguaje procedimental.

Java es un lenguaje con una funcionalidad muy extensa, y aunque coincide con C en un porcentaje amplio de esta funcionalidad las diferencias entre ambos son mucho mayores que las coincidencias. Las construcciones del lenguaje C son mucho más simples que las de Java. En mecanismos tales como bucles, condicionales, etc., ambos lenguajes coinciden, pero en el resto de aspectos tales como la definición de estructuras de datos C ofrece mecanismos más simples.

1.1. Un programa en C

Un programa en C es un conjunto de funciones, definiciones de tipos de datos y declaraciones de variables contenidos en un conjunto de ficheros. Un programa en C siempre comienza a ejecutar por la función con nombre main. Cualquier función puede llamar a cualquier función, y las variables declaradas fuera de las funciones tienen ámbito global o local al fichero que las contiene (si se declaran con el prefijo static). La siguiente figura ilustra la estructura de un programa en C contenido en varios ficheros.

El compilador es el programa encargado de traducir un conjunto de funciones, definiciones y declaraciones en múltiples ficheros a un fichero ejecutable. El compilador de C tiene un comportamiento sorprendentemente sencillo y realiza mucho menos trabajo del que se está acostumbrado con otros compiladores como el de Java. Para crear un ejecutable, el compilador procesa los ficheros de código uno a uno de forma independiente, de forma que las definiciones de variables y funciones de un fichero no las recuerda cuando procesa el fichero siguiente. Además, el compilador realiza una única pasada por el texto, por lo que cuando se traduce un fichero, en un punto concreto del texto, sólo se dispone de las definiciones que se han encontrado hasta ese punto.

Como consecuencia de este comportamiento, una variable no puede ser utilizada, a no ser que se haya declarado previamente en ese mismo fichero. Igualmente, una función no puede ser invocada a no ser que se haya incluido previamente en el mismo fichero. Para poder dividir igualmente el código en múltiples ficheros el lenguaje permite la definición de prototipos de funciones (el tipo del resultado que devuelven, seguido del nombre de la función y del tipo de sus parámetros entre paréntesis) sin incluir el código, y además permite la definición de variables como externas, es decir, que están en otro fichero. A continuación se muestran dos ficheros en los que la función fill_in y la variable table están definidas en un fichero pero las utiliza la función main en el otro fichero.

Fichero1.c Fichero2.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define SIZE 100
/* Tabla de 100 enteros (Global) */
int table[SIZE];

/* Rellena una tabla con ceros */
void fill_in(int *t, int size) 
{
    int i;
    for (i = 0; i < size; i++) 
    {
        t[i] = 0;
    }
    return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#define SIZE 100
/* Variable global declarada en otro fichero */
extern int table[SIZE];

/* Función declarada en otro fichero */
void fill_in(int *, int);

/* Punto de entrada del programa */
int main(int argc, char *argv[]) 
{
    fill_in(table, SIZE);
    return 0;
}

La línea 3 en Fichero2.c notifica al compilador que existe una tabla de 100 enteros con nombre table definida en otro fichero. La línea 6 es un prototipo de función. Contiene el tipo del resultado (void) seguido del nombre de la función (fill_in) y de los nombres y tipos de los parámetros entre paréntesis (int *t, int size). Esta línea informa al compilador que una función de estas características está definida en otro lugar de del programa. Gracias a estas definiciones la línea 11 es correcta. La función fill_in puede ser invocada, y la variable table es conocida.

La línea 1 de ambos ficheros (#define) es una directiva del preprocesador, que indica que tiene que cambiar todas las ocurrencias de una cadena de caracteres dada (en este caso SIZE) por un valor específico (en este caso, 100). El preprocesador corre automáticamente antes que el compilador y se encarga de estas sustituciones. Usa la directiva #define siempre que vayas a definir constantes en tu programa, sobre todo para los tamaños de arrays, y escribe siempre esas constantes en mayúsculas, de forma que sean fáciles de leer y modificar.

Responde a las siguientes preguntas para ver si has entendido lo que se explica en este documento:

  1. Un programa en C es un conjunto de definiciones de tres tipos: variables, funciones y tipos de datos.

    • Verdadero

    • Falso

  2. Algunas funciones y variables se pueden agrupar en clases, como en Java.

    • Verdadero

    • Falso

  3. Puedo utilizar una función en un fichero y luego definirla (incluir su código) en un lugar más abajo de ese fichero.

    • Verdadero

    • Falso

  4. Como una función no puede llamar a otra que no se haya definido previamente, en C no se pueden escribir dos funciones que se llamen la una a la otra.

    • Verdadero

    • Falso

  5. ¿Qué es el prototipo de una función?

    • Una línea en el programa que tiene sólo su nombre.

    • Una línea de código con el nombre, los parámetros de la función y el resultado que devuelve.

    • La definición de la función: nombre y parámetros seguido de su código.

    • Una descripción en papel de lo que debe hacer una función para luego implementarla.