неявный заголовок включает в C - PullRequest
2 голосов
/ 02 апреля 2012

program.c :

int main () {

    hello();
    return 0;
}

tools.c :

void hello (void) {

    printf("hello world\n");
}

Makefile :

program : program.o tools.o

В наборе файлов этой программы у меня нет файла tools.h , хотяон прекрасно компилируется без ошибок, может кто-нибудь объяснить назначение заголовочных файлов в программах на Си?

Пока у меня есть только одна идея: переменные, такие как структуры, требуются на уровне компиляции ...

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

Ответы [ 6 ]

1 голос
/ 02 апреля 2012

В C вам разрешено вызывать функцию, которая не была объявлена, предполагается, что это extern-функция, которая возвращает int, а компилятор позволит вам передавать любое количество аргументов любого типа. Обратите внимание, что это не рекомендуется.

Заголовочные файлы необходимы для информирования компилятора C о правильной подписи, особенно о типе возвращаемого значения. Если объявление существует, компилятор проверит, что переданные аргументы соответствуют объявлению, хотя в очень старом стиле C проверяется только количество аргументов, а не их типы. Поскольку вы не можете перегружать функции в C, я считаю, что это допустимо (хотя и не рекомендуется, и может фактически не работать во время выполнения в зависимости от используемого соглашения о вызовах) для объявления функции:

int hello();

А затем фактически реализовать это как:

int hello(char* who) {
    printf("Hello %s\n", who);
}

Компоновщик свяжет эти вещи вместе. Обратите внимание, что это не очень хороший стиль.

Обратите внимание, что это одно из ключевых изменений в C ++, вы должны объявлять функции перед их вызовом и проверять типы всех аргументов.

1 голос
/ 02 апреля 2012

Заголовочные файлы обычно содержат объявления функций, которые определены в исходных файлах c.

Какую цель он служит?

  • Это дает вам дополнительную безопасность, компилятор проверяет параметры, переданные функции, по объявлению и сообщает об ошибках, если обнаруживает несоответствие.
  • Они позволяют отделить интерфейс от реализации. В основном, это позволяет вам предоставлять свой код (реализацию) в виде библиотеки, с которой клиенты должны ссылаться, просто включив файл заголовка интерфейса в свои приложения.
0 голосов
/ 02 апреля 2012

Из GCC документов :

Заголовочные файлы служат двум целям.

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

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

Я считаю, что заголовки C являются устаревшимиартефакты ранних дней C.

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

  • Для того, чтобы сохранить исполняемые файлы небольшими, информация о типе не внедряется в объектный код, сгенерированный компиляторами Си.Из-за этого для связи с объектным файлом или библиотекой требуется определение типов, которые предоставляет каждая функция, так как эта информация недоступна из объектного файла.Современные компиляторы обходят это, проверяя исходный код или библиотеки на предмет определений. Идентификаторы и подписи извлекаются непосредственно из исходного кода или символов библиотеки.

  • Чтобы компиляторы были простыми и эффективными, было бынеуместно вводить определения проверок из библиотеки, с которой они будут связаны.Действительно, вполне допустимо скомпилировать файл без использованной библиотеки, даже если он недоступен на компьютере компиляции.Точно так же было бы обременительно откладывать компиляцию типа до тех пор, пока не будут скомпилированы его зависимости, и совершенно невозможно в случае циклических отношений.Для этого необходимо, чтобы сигнатура функции была доступна до ее использования (для проверки типа).Для удобства C по умолчанию равен int fun(...), так что в наиболее распространенном на тот момент случае необходимость в предварительном объявлении функций уменьшается.

Чтобы облегчить жизнь всем, ручные декларации былиделегирован препроцессору.По сути, компилятор C не имеет концепции заголовочного файла.Вместо этого объявления логически организованы в файлы заголовков, которые препроцессор добавляет в программу предварительной компиляции.

Это избавляет программиста от необходимости вводить все необходимые объявления в начале каждой единицы компиляции,но это, по сути, то, что происходит.

Все эти искажения на самом деле являются результатом ограничений, которые существовали в первые дни C.

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

Современные языки имеют конструкции более высокого уровня для отдельного интерфейса формы реализации: интерфейсы в Java, утка, в Python, Протоколы в Clojure, контракты в Эйфелевой и т. Д. *

0 голосов
/ 02 апреля 2012

Без прототипа для области действия hello() во время вызова компилятор предполагает (с предупреждением при правильной настройке) прототип int hello() (примечание: не int hello(void)).

Но определение не согласуется с этим прототипом: void hello(void) против int hello(), поэтому вы только что развязали Undefined Behavior. Все может случиться. В частности, ваша программа может компилироваться и запускаться так, как вы этого ожидаете.

Вы можете избежать этого UB , предоставив правильный прототип, либо написав и включив файл заголовка, либо указав прототип непосредственно в исходном файле program.c.

0 голосов
/ 02 апреля 2012

В вашем примере вы компилируете оба вместе. Так что все в порядке, и вы не получаете никакой ошибки, несмотря на пропущенное предварительное объявление "hello".

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

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

0 голосов
/ 02 апреля 2012

Они позволяют вам делиться объявлением функций, переменных, структур .. между различными c файлами.

...