Как мне заставить make выяснить правильные зависимости для ссылки в правильных нижестоящих объектных файлах? - PullRequest
1 голос
/ 27 сентября 2010

Я собираюсь использовать небольшой пример для справки.Рассмотрим проект с:

inner_definitions.o : inner_definitions.cpp inner_definitions.h
    gcc $^ -o $@

inner_class_1.o : inner_class_1.cpp inner_class_1.h inner_definitions.h
    gcc $^ -o $@    

inner_class_2.o : inner_class_2.cpp inner_class_2.h inner_definitions.h
    gcc $^ -o $@

outer_class.o : outer_class.cpp outer_class.h inner_class_1.h inner_class_2.h
    gcc $^ -o $@

executable.o : executable.cpp executable.h outer_class.h
    gcc $^ -o $@

executable : __?1__
    __?2__

Но заполнить пробелы __?1__ для зависимостей компоновщика и __?2__ для команды компоновщика нелегко.

В этом небольшом примереМожно утверждать, что легко увидеть, что __?1__ = inner_definitions.o inner_class_1.o inner_class_2.o outer_class.o executable.o.Однако это явно не масштабируемое решение, поскольку оно заставляет каждого разработчика понимать все зависимости кода, с которым он работает, чтобы они могли выяснить зависимости вручную , а не с помощью утилиты make.

Другим решением будет иметь разные переменные для каждого объектного файла, в котором перечислены все его нисходящие зависимости: то есть __?1__ = executable.o $(executable_dependencies).Это не желаемое решение, потому что оно заставляет make-файл компилироваться особым образом, поэтому переменные используются только тогда, когда они полностью определены.Кроме того, для действительно больших приложений эти переменные могут превышать максимальную длину переменной.

Еще одним решением является использование архива .a файлов для компоновки.В этом случае мы могли бы создать inner_class_1.a, который включал бы и inner_defintions.o, и inner_class_1.o, так что он мог бы быть связан с любым объектным файлом, который нуждался в inner_class_1.o, не заставляя разработчика восстанавливать зависимости.Этот подход кажется многообещающим, но предполагает наличие множества дублирующих файлов.Также не похоже, что компоновщик gcc может обрабатывать вложенные архивные файлы.

Есть ли другой подход?Каков наилучший подход?Может ли компоновщик gcc обрабатывать вложенные архивные файлы?

Ответы [ 2 ]

2 голосов
/ 28 сентября 2010

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

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

Компоновщик GNU не будет извлекать объекты из вложенных библиотек. Если вы хотите создать одну большую библиотеку, объединив много маленьких, вы можете сделать это с помощью команды «addlib» в ar script . Это даст вам один архив, содержащий все объектные файлы без какой-либо вложенной структуры библиотеки.

Если вас беспокоит дублирование наличия файлов .o и .a, содержащих один и тот же объектный код, в прекрасном руководстве описан способ прямого обновления архивов .

.
0 голосов
/ 28 сентября 2010

Ваш make-файл должен иметь список объектов, которые нужно связать вместе, например:

OBJ_FILES = inner_definitions.o inner_class_1.o inner_class_2.o \ 
outer_class.o executable.o

executable : $(OBJ_FILES)
    gcc $^ -o $@

Кто-то должен написать это; GCC не может сделать это для вас, Make не может сделать это для вас. Не каждый разработчик проекта должен знать, как создать этот список, а только тот, кто пишет эту часть make-файла. Все остальные будут использовать этот make-файл, и разработчик, который добавляет новую зависимость (например, inner_class_3), может добавить ее в список.

И если ваш make-файл потерян в огне, и единственный разработчик, который знает все зависимости, поражен шиной, действительно нетрудно восстановить список: когда вы пытаетесь создать executable, компоновщик жалуется что foo::bar() не определено, вы осматриваетесь и обнаруживаете, что foo::bar() определено в inner_class_2.cpp, вы добавляете inner_class_2.o в список. Повторяйте, пока компоновщик не перестанет жаловаться.

P.S. Как только все будет в порядке, вы можете значительно упростить остальную часть make-файла:

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

inner_class_1.o inner_class_2.o : inner_definitions.h

outer_class.o : inner_class_1.h inner_class_2.h

executable.o : outer_class.h

EDIT:

  1. Метод, который я предложил, не требует перечисления всех объектных файлов, которые могут быть созданы, только тех, которые действительно необходимы для создания `исполняемого файла`; Я вывел список из вашего вопроса.
  2. Передача дополнительных объектных файлов компоновщику не имеет значения для конечного исполняемого файла, но приводит к ненужной перестройке. Например, предположим, что вы добавили `alien.o` в` OBJ_FILES`. Затем, если вы измените `alien.cpp` и запустите` make исполняемый файл`, он восстановит `alien.o` и` исполняемый файл`, даже если в этом нет особой необходимости. Исправление (благодаря slowdog): ненужные объектные файлы попадают в конечный исполняемый файл как мертвый код - но я все еще прав насчет ненужной перестройки.
  3. Организация объектных файлов в архивах и общих библиотеках часто удобна, но здесь ничего не меняется.
  4. Я не знаю надежного способа автоматического построения списка объектов, то есть способа, который мог бы иметь дело с проблемными случаями, например, когда одна и та же функция определена в двух разных исходных файлах. Это может стать реальной проблемой, если участвуют юнит-тесты. Но вы можете сделать это в вашем make-файле , если вы следуете простому соглашению об именах.
  5. Хитрость для этого в вашем make-файле довольно продвинутая. Честно говоря, я думаю, что вам будет лучше сделать это простым способом, пока вы не освоитесь с инструментами.

EDIT:
Хорошо, вот схема продвинутой техники.

Сначала рассмотрим все включенные заголовочные файлы. Было бы неплохо, чтобы Make обрабатывал зависимости, а не вставлял их вручную, как в приведенном выше make-файле. И это простая задача: если X.cpp #include Y.h (напрямую или через цепочку заголовочных файлов #included), то X.o будет зависеть от Y.h. Это уже было разработано как «Advanced Auto-Dependency Generation» . Но если вы следуете строгому соглашению об именах, вы можете сделать еще один шаг: если все, что объявлено, но не определено в Xh, определено в X.cpp, то, следуя тому же дереву операторов #include, мы сможем создать список необходимых объектных файлов, которые затем будут зависимостями executable.

Это действительно много, чтобы поглотить сразу, поэтому я не буду пытаться проработать пример. Я предлагаю вам просмотреть документ и посмотреть, как он может генерировать зависимости Y.h, затем попытаться применить его к примеру make-файла, а затем подумать, что должен делать «шаг вперед».

Позже вы можете применить его к тестовому устройству, где объектные файлы, например, outer_class.o, stub_inner_class_1.o, stub_inner_class_2.o и test_outer_class.o.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...