Несколько исходных файлов в C- Как именно работают make-файлы? - PullRequest
26 голосов
/ 03 декабря 2010

Я относительный новичок в C, и мне нужно узнать, как работают make-файлы, и я немного запутался в том, как работает комбинация файлов C. Скажем, у нас есть main.c, foo.c и bar.c. Как должен быть написан код, чтобы main.c распознавал функции в других файлах? Кроме того, в foo.c и bar.c весь ли код написан в главной функции или нам нужно написать другие функции для того, что нам нужно? Я читал учебные пособия о том, как пишутся make-файлы, и по большей части это имеет смысл, но я все еще немного запутался в базовой логистике этого.

Ответы [ 6 ]

27 голосов
/ 03 декабря 2010

Как правило, вы будете определять свои функции для других файлов в заголовочном файле, который затем может быть включен в main.c. Например, рассмотрим следующие фрагменты:

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");
}

Что произойдет, это то, что main.c будет превращен в объектный файл (main.o), а foo.c будет превращен в объектный файл (foo.o). Затем компоновщик свяжет эти два файла вместе, и именно здесь функция do_foo() в main.c «связана» с функцией в foo.o.

Пример команды GCC : gcc -o myprogram main.c foo.c

Пример 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
16 голосов
/ 03 декабря 2010

О компиляции C / C ++

Когда у вас есть набор файлов, вы обычно делаете 2 вещи:

  • скомпилировать каждый исходный файл (.c) в объектный файл (.o)
  • связывает все объектные файлы в исполняемый файл.

Исходные файлы независимы - вам нужно заголовочные файлы , чтобы иметь возможность предоставлять "информацию" (объявления) о функциях в данном модуле, чтобы позволить любому другому модулю используй их. Заголовочные файлы не компилируются сами по себе - они #include d являются частями исходных файлов.

Посмотрите ниже, как выглядят команды для этого и как они обрабатываются make.


О makefiles

Makefile - это набор целей и правил для их построения. target - это «нечто, что может быть построено и приводит к заданному файлу». (Существуют также «фальшивые» цели, которые не приводят к файлу и просто предназначены для выполнения команд - общая называется clean для удаления результатов компиляции).

Каждая цель состоит из 2 частей:

  • список зависимостей , «список чувствительности» (другие файлы и цели, необходимые для этой цели) (после :, через запятую),
  • список команд оболочки , которые выполняются для построения этой цели (ниже указанного, с отступом)

Рассмотрим этот пример:

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

Это означает, что: "Чтобы создать файл main, мне нужно сначала убедиться, что цели main.o, module1.o и module2.o обновлены; затем мне нужно вызвать следующую команда ... ".

Это также можно переписать как:

main: main.o module1.o module2.o
    gcc $^ -o $@

Переменные (все, что начинается с $ - это переменные) будут расширены до списка зависимостей и целевого имени, как вы ожидаете.

Вы можете определить свои собственные переменные и расширить их следующим образом:

OBJS = main.o module1.o module2.o

main: $(OBJS)
    # code goes here

Вы компилируете отдельные единицы перевода следующим образом:

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

Вы можете добавить зависимости заголовка для перестройки main.o, когда изменяется либо main.c, либо любой из его заголовков:

main.o: main.c module1.h module2.h
    gcc -c $< -o $@

Чтобы не писать одну и ту же команду снова и снова, вы можете определить общее правило и просто указать зависимости (если хотите):

%.o: %.c
    gcc -c $< -o $@

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

Существует также некоторая магия для автоматического создания зависимостей (см. Ссылку) . Одним из недостатков использования Make является то, что он не делает это сам по себе (как это делают некоторые системы сборки - например, SCons, который я предпочитаю для C / C ++).

2 голосов
/ 04 декабря 2010

По сути, make-файл состоит из правил вида:

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

Обычно у вас есть хотя бы одна цель высокого уровня, если какой-либо из ее зависимостей не существует, make ищет правило с такимфайл в качестве цели.Он делает это рекурсивно, пока не разрешит все зависимости цели верхнего уровня, перед выполнением команды верхнего уровня (если она есть - и зависимости, и команда являются необязательными полями в правиле)

Файл makeможет иметь «правила по умолчанию», основанные на шаблонах, и есть встроенные макросы для различных сценариев сопоставления файлов, а также пользовательские макросы и включение вложенных make-файлов.

Я упростил приведенную выше форму правила для большинстваобычный случай.Фактически команде вообще не нужно создавать цель, это просто команда, которая должна быть выполнена, как только все файлы в зависимости присутствуют.Более того, цель не обязательно должна быть файлом.Часто целью верхнего уровня является «фиктивная» цель, называемая «все» или похожая.

Конечно, есть много тонкостей и нюансов, все подробно описано в руководстве (GNU делает специальноЕсть и другие утилиты make).

2 голосов
/ 03 декабря 2010

Функции в foo.c, которые необходимо вызывать извне foo.c, должны иметь прототипы в foo.h.Внешние файлы, которые должны вызывать эти функции, должны затем #include "foo.h".foo.c и bar.c не должны даже иметь функцию main(), если они являются частью той же программы, что и main.c.

Makefiles определяют цели.Для простых программ у вас может быть только одна цель, которая компилирует все это.Более сложные (читай: большие) программы могут иметь промежуточные цели (например, foo.o), которые позволят make избежать ненужной перекомпиляции.Способ make определяет, нужно ли перекомпилировать заданную цель, если он просматривает время модификации всех предварительных условий (материал после двоеточия) и если какой-либо из них наступает после последнего измененного времени самого целевого файла, он восстанавливается.

Вот очень простой пример:

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

Затем вы можете запустить make main и он скомпилирует foo.c в foo.o, затем скомпилирует main.c и свяжет его сfoo.o.Если вы измените main.c, он просто перекомпилирует main.c и свяжет его с уже созданным foo.o.

1 голос
/ 19 марта 2015

Как работает Makefile?

=> Когда команда make выполняется на терминале, она ищет файл с именем makefile или Makefile в текущем каталоге и создает дерево зависимостей.

Если у вас есть несколько файлов Makefile, вы можете выполнить определенные команды:

                           make -f MyMakefile

=> На основе make target, указанного в make-файле, проверять, существуют ли файлы зависимостей для этого target. И, если они существуют, сравнивают ли они временные метки файлов, если они новее, чем сама цель.

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.

=> Перед выполнением команд соответствующей цели, ее зависимости должны быть выполнены, если они не достигнуты, цели этих зависимостей выполняются до заданной цели make, чтобы предоставить недостающие зависимости.

=> Если целью является имя файла, make сравнивает временные метки целевого файла и его файлов зависимостей. Если файл зависимости новее, чем целевой файл, в противном случае целевое выполнение не будет выполнено.

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.

=> Таким образом, он рекурсивно проверяет весь путь по дереву зависимостей до файлов исходного кода. Посредством этого процесса make экономит время, выполняя только те команды, которые необходимо выполнить, в зависимости от того, какие из исходных файлов (перечислены как зависимости) были обновлены и имеют более новую временную отметку, чем их цель.

=> Теперь, когда цель не является именем файла (которое мы назвали «специальными целями»), make, очевидно, не может сравнивать метки времени, чтобы проверить, являются ли зависимости цели более новыми. Поэтому такая цель всегда выполняется.

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.

Для выполнения каждой цели make печатает действия при их выполнении. Обратите внимание, что каждая команда выполняется в отдельной среде под-оболочки из-за безопасного выполнения, так что они не могут изменить текущую среду оболочки, которая может повлиять на выполнение другой цели. Например, если одна команда содержит cd newdir, текущий каталог будет изменен только для этой команды строки, для следующей команды строки текущий каталог не изменится.

Источник: - http://www.firmcodes.com/linux/write-first-makefile-c-source-code-linux-tutorial/

1 голос
/ 03 декабря 2010

Make имеет мало общего со структурой C-программы. Все, что делает make, - это определяет дерево зависимостей и выполняет команды, когда обнаруживает, что зависимости вышли из строя. Моя поговорка в make-файле:

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

просто sez: foo.exe зависит от foo.c, bar.c и baz.c. Это, sotto vocce , расширяется, используя набор правил make по умолчанию, до чего-то вроде:

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

foo.obj : foo.c

bar.obj : bar.c

baz.obj : baz.c

Make просто просматривает дерево зависимостей, начиная с его корня (в данном случае, foo.exe). Если цель не существует или если один из объектов, от которых она зависит, является более новым, чем цель, выполняются соответствующие команды. сделать зависимость правильной.

См. Управление проектами с помощью Make от O'Reilly, чтобы узнать больше, чем вы, вероятно, хотели бы знать.

Что касается второй части вашего вопроса, то ответ состоит из двух букв: K и R . Их Язык программирования C , пожалуй, одна из лучших книг по программированию, когда-либо написанных.

alt text

...