2010-12-03 21 views
20

Soy un principiante relativo a C y necesito aprender cómo funcionan los makefiles y estoy un poco confundido sobre cómo funciona la combinación de archivos C. Digamos que tenemos un main.c, un foo.c y un bar.c. ¿Cómo debe escribirse el código para que main.c reconozca funciones en los otros archivos? Además, en foo.c y bar.c, ¿está todo el código escrito en la función principal allí o necesitamos escribir otras funciones para lo que necesitamos que hagan? He leído tutoriales sobre cómo se escriben los archivos make, y tiene sentido en su mayor parte, pero todavía estoy un poco confundido sobre la logística básica.Múltiples archivos fuente en C- ¿Cómo funcionan exactamente los makefiles?

+1

Sólo una nota, no una respuesta en toda regla: En realidad no quiere tener una función 'main' en más de un archivo que se compila en el mismo objetivo, como entonces Tendrás dos funciones con el mismo nombre. –

Respuesta

24

En general, lo que sucederá es que definirá sus funciones para los otros archivos en un archivo de encabezado, que luego se puede incluir en main.c. Por ejemplo, considere los siguientes fragmentos:

main.c:

#include "foo.h" 

int main(int argc, char *argv[]) { 
    do_foo(); 
    return 0; 
} 

foo.h:

void do_foo(); 

foo.c:

#include <stdio.h> 
#include "foo.h" 

void do_foo() { 
    printf("foo was done\n"); 
} 

Lo que sucederá es que el principal .c se convertirá en un archivo de objeto (main.o) y foo.c se convertirá en un archivo de objeto (foo.o). Entonces el enlazador vinculará estos dos archivos y ahí es donde la función do_foo() en main.c está 'asociada' con la función en foo.o.

Ejemplo GCC comando: gcc -o miprograma main.c foo.c

Ejemplo makefile

myprogam: main.o foo.o 
    gcc -o myprogram main.o foo.o 

main.o: main.c foo.h 
    gcc -c main.c 

foo.o: foo.c foo.h 
    gcc -c foo.c 
+2

Sangrar el archivo Makefile con pestañas reales como se menciona aquí http://stackoverflow.com/a/9580615/1461060 – gihanchanuka

2

Las funciones en foo.c que deben llamarse desde fuera foo.c deben tener prototipos en foo.h. Los archivos externos que necesitan llamar a esas funciones deben entonces #include "foo.h". foo.c y bar.c ni siquiera deberían tener una función main() si son parte del mismo programa que main.c.

Los archivos Makefiles definen objetivos. Para programas simples, puede tener un solo objetivo que compila todo. Los programas más complejos (léase: más grandes) pueden tener objetivos intermedios (como foo.o) que permitirán que make evite la recompilación innecesaria. La forma en que make determina si un objetivo determinado necesita o no ser recompilado es mirando los tiempos de modificación de todos los prerrequisitos (lo que está detrás de los dos puntos) y si alguno de ellos viene después de la última hora cambiada del archivo objetivo, se reconstruye

Aquí hay un ejemplo muy sencillo:

main.c:

#include "foo.h" 

int main() 
{ 
    fooprint(12); 
    return 0; 
} 

foo.c:

#include "stdio.h" 
#include "foo.h" 

void fooprint(int val) 
{ 
    printf("A value: %d\n", val); 
} 

foo.h:

void fooprint(int val); 

Makefile:

main: main.c foo.o 
    gcc -o main main.c foo.o 

foo.o: foo.c 
    gcc -c foo.c 

continuación, puede ejecutar make main y se compilará en foo.cfoo.o a continuación, compilar main.c y vincularlo con foo.o. Si modifica main.c, simplemente recompilará main.c y lo vinculará con el ya construido foo.o.

+0

¿Debería su primera oración leer "Funciones en foo.c que necesitan ser llamadas desde fuera de foo.c deben tener prototipos en foo.h", omitiendo el "no hacer" y de ese modo invertir lo que realmente dice? Los corolarios son (1) funciones que no necesitan ser llamadas desde fuera de foo.c deben definirse como funciones estáticas, y (2) en paralelo, funciones en barra.c que necesitan ser llamadas desde fuera de barra.c deben tener prototipos en bar.h (y funciones dentro de bar.c que no deberían ser llamadas desde afuera bar.c deberían definirse como funciones estáticas). –

+0

@Jonathan: Whoops, sí. Iba a hacer ese punto sobre 'estático' pero decidí no hacerlo. – nmichaels

+0

El OP pregunta al menos tanto sobre la organización del programa como sobre los archivos make; Creo que la educación temprana sobre 'estático debería ser el predeterminado' es una buena idea, todo lo que puede ser estático debería ser. El otro error común para principiantes es 'todas las declaraciones utilizadas en foo.c deben estar en foo.h', en lugar de afirmar que 'foo.h le dice al mundo exterior lo que necesitan saber para usar las funciones que foo.c define para ellas usar'. Por ejemplo, los tipos utilizados solo dentro de foo.c no deberían definirse en foo.h, aunque con mucha frecuencia lo son. –

13

Acerca de C/C++ compilación

Cuando se tiene un conjunto de archivos, generalmente hace 2 cosas:

  • compilar cada archivo de origen (.c) en un archivo de objeto (.o)
  • vincular todos los archivos de objeto en un archivo ejecutable.

Los archivos de origen son independientes - que necesita archivos de cabecera a ser capaz de proporcionar la "información" (declaraciones) acerca de las funciones en un módulo dado con el fin de permitir que cualquier otro módulo los utilizan. Los archivos de encabezado no se compilan solos: son #include d como partes de los archivos de origen.

A continuación, le mostramos cómo se ven los comandos y cómo se manejan en make.


Acerca de los archivos make

Makefile es un conjunto de objetivos y reglas para construirlas. Un objetivo es "algo que se puede generar y da como resultado un archivo dado". (También existen objetivos "falsos" que no dan como resultado un archivo y solo están ahí para ejecutar comandos; uno común se llama clean para eliminar los resultados de la compilación).

Cada objetivo tiene 2 partes:

  • lista de dependencias, "lista de sensibilidad" (otros archivos y objetivos que son necesarios para este objetivo) (después de :, separados por comas),
  • lista de comandos de shell que se ejecutan para construir este objetivo (debajo de lo anterior, con sangría)

Considere este ejemplo:

main: main.o module1.o module2.o 
    g++ main.o module1.o module2.o -o main 

Esto significa que: "Para construir el archivo main, necesito primero asegurarse de que los objetivos main.o, module1.o y module2.o son hasta la fecha; entonces necesito llamar al siguiente comando ... ".

Esto puede ser también reescribirse como:

main: main.o module1.o module2.o 
    gcc $^ -o [email protected] 

Las variables (todo lo que empieza con $ es una variable) se ampliará a la lista de dependencias y el nombre de destino, como se esperaba.

Puede definir sus propias variables y ampliarlos de la siguiente manera:

OBJS = main.o module1.o module2.o 

main: $(OBJS) 
    # code goes here 

compila unidades de traducción individuales de la siguiente manera:

main.o: main.c 
    gcc -c $< -o [email protected] 
    # note the -c option, which means: "compile, but don't link" 
    # $< will expand to the first source file 

Usted puede agregar dependencias de cabecera para reconstruir main.o cuando cualquiera de main.c o alguno de sus encabezados cambian:

main.o: main.c module1.h module2.h 
    gcc -c $< -o [email protected] 

Con el fin de no escribir el mismo comando una y otra vez, se puede definir una regla general y simplemente suministrar las dependencias (si lo desea):

%.o: %.c 
    gcc -c $< -o [email protected] 

main.o: main.c module1.h module2.h 
module1.o: module1.c module1.h module2.h 

También hay algo de magia para generar el dependencies automatically (see link) . Una de las desventajas del uso de Make es que no lo hace por sí mismo (como hacen algunos sistemas de construcción, como SCons, que prefiero para C/C++).

1

Hacer tiene poco que ver con la estructura de un programa en C. All make does define un árbol de dependencias y ejecuta comandos cuando encuentra que las dependencias están fuera de control. Mi frase, en un makefile:

foo.exe : foo.c bar.c baz.c 

simplemente Sez: El foo.exe depende de foo.c, bar.c y baz.c. Esto, Vocce sotto, se amplió, utilizando el conjunto de reglas por defecto de marca, a algo como:

foo.exe : foo.obj bar.obj baz.obj 

foo.obj : foo.c 

bar.obj : bar.c 

baz.obj : baz.c 

Hacer simplemente recorre el árbol de dependencias a partir de su raíz (en este caso, foo.exe). Si un objetivo no existe o si uno de los objetos de los que depende es más nuevo que el objetivo, se ejecutan los comandos asociados. para hacer la dependencia correcta

Consulte Managing Projects with Make de O'Reilly para obtener más de lo que probablemente quiera saber.

En cuanto a la segunda parte de su pregunta, la respuesta es solo dos letras: K and R. Su The C Programming Language es posiblemente uno de los mejores libros de programación de computadora jamás escritos.

alt text

2

En esencia, un makefile consta de reglas de la forma:

<this file> : <needs these files> 
    <and is created by this command> 

normalmente tiene al menos un objetivo de alto nivel, si cualquiera de sus dependencias no existen, que busca una regla que tiene ese archivo como un objetivo.Lo hace recursivamente hasta que haya resuelto todas las dependencias del objetivo de nivel superior, antes de ejecutar el comando de nivel superior (si hay uno, tanto las dependencias como el comando son campos opcionales en una regla)

Un archivo make puede tener 'reglas predeterminadas' basadas en patrones, y hay macros integradas para varios escenarios de coincidencia de archivos, así como macros de definición de usuario e inclusión de archivos make anidados.

He simplificado la forma de regla anterior al caso más habitual. De hecho, el comando no necesita crear el objetivo en absoluto, es simplemente un comando que se ejecutará una vez que todos los archivos en la dependencia estén presentes. Además, el objetivo no necesita ser un archivo tampoco. A menudo, el objetivo de nivel superior es un objetivo "ficticio" llamado "todos" o similar.

Por supuesto, hay muchas sutilezas y matices para hacer, todos detallados en the manual (GNU hace específicamente, hay otras utilidades make).

1

¿Cómo funciona Makefile?

=> Cuando el comando make se ejecuta en el terminal, busca un archivo llamado makefile o Makefile en el directorio actual y construye un árbol de dependencias.

Si tiene varios ficheros Makefile, a continuación, se puede ejecutar el comando específico con:

      make -f MyMakefile 

=> Sobre la base de maquillaje de destino especificado en el Makefile, que comprueba si existen los archivos de dependencia de ese objetivo. Y si existen, si son más nuevos que el objetivo en sí, al comparar las marcas de tiempo del archivo.

Here our first and default target is “all” which looks for main.o and function.o file dependencies. Second and third target is main.o and function.o respectively which have dependencies of main.c and function.c respectively. 

=> Antes de ejecutar los comandos de destino correspondiente, sus dependencias se deben cumplir, cuando no se cumplen, los objetivos de esas dependencias se ejecutan antes que el blanco marca determinada, para suministrar las dependencias faltantes.

=> Cuando un destino es un nombre de archivo, haga una comparación de las marcas de tiempo del archivo de destino y sus archivos de dependencia. Si el archivo de dependencia es más reciente que el archivo de destino, el objetivo ejecutará de lo contrario no se ejecutará.

In our case, when first target “all” start executing it looks for main.o file dependency, if its not met. Then it goes to second target main.o which check for its dependency main.c and compare time-stamp with it. If target found main.c dependency is updated, then target execute else not. Same process is follow for next target function.o. 

=> De este modo termina la comprobación de forma recursiva hasta el fondo del árbol de dependencias, a los archivos de código fuente. Con este proceso, ahorra tiempo al ejecutar solo los comandos que deben ejecutarse, en función de cuáles de los archivos fuente (enumerados como dependencias) se han actualizado y tienen un sello de tiempo más nuevo que su objetivo.

=> Ahora, cuando un objetivo no es un nombre de archivo (que llamamos "objetivos especiales"), obviamente no se pueden comparar las marcas de tiempo para comprobar si las dependencias del objetivo son más recientes. Por lo tanto, dicho objetivo siempre se ejecuta.

In our Makefile, special targets are “all” and “clean”. As we discussed target “all” earlier, but we not discuss target clean. Target clean removes the all object files created during compilation and binary executable files according to command. 

Para la ejecución de cada objetivo, imprima las acciones mientras las ejecuta. Tenga en cuenta que cada uno de los comandos se ejecuta en un entorno de subconjunto separado debido a la ejecución segura, de modo que no pueden cambiar el entorno del shell actual, lo que puede afectar a la ejecución de otro objetivo. Por ejemplo, si un comando contiene cd newdir, el directorio actual se cambiará solo para ese comando de línea, para el siguiente comando de línea, el directorio actual no se modificará.

Fuente: - http://www.firmcodes.com/linux/write-first-makefile-c-source-code-linux-tutorial/

Cuestiones relacionadas