Где функция, объявленная в файле .h, находит свое определение? - PullRequest
2 голосов
/ 03 августа 2020

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

(1) Я знаю, что по соглашению файл заголовка не должен включать определения функций. Но в этом простом коде:

#include<stdio.h>
int main(){
    printf("123");
}

printf функция объявлена ​​в stdio.h, однако я не определял вручную функцию printf. Почему код успешно компилируется? Разве это не должно вызывать ошибку компиляции, например, function printf is not defined?

(2) Предположим, у меня есть заголовочный файл foo.h, в котором объявляется функция foo

int foo(int num);

I есть файл main.c ниже в том же каталоге, что и foo.h:

#include <stdio.h>
#include "foo.h"
int main(){
    printf("%d",foo(123));
}

Мой вопрос: где мне определить функцию foo? Есть ли другое место, кроме main.c?

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

Ответы [ 4 ]

2 голосов
/ 03 августа 2020

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

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

Отдельная компиляция имеет ряд преимуществ:

  • Различные части приложения могут быть протестированы независимо друг от друга;
  • Код можно легко использовать в разных проектах;
  • Части приложения могут быть повторно реализованы, не затрагивая другие части.

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

Также обычно определяют новые типы (например, тип FILE) и макросы (например, макрос NULL) в файлах заголовков, чтобы эти определения могли легко быть #include d и используется вашим кодом.

Трудно увидеть утилиту, пока вы не поработаете над большим проектом.

2 голосов
/ 03 августа 2020

Почему код успешно компилируется?

Потому что ваш компилятор автоматически связывает стандартную C библиотеку. Стандартная библиотека C содержит определение функции printf.

Разве не возникает ошибка компиляции, например, функция printf не определена?

Нет , компилятор должен предоставить определения для стандартных символов, и printf является одним из них (в размещенной среде).

Мой вопрос: где мне определить функцию foo?

Где угодно хотите. В main.c или в другом .c файле по вашему желанию. Обычно это хорошая практика, я ожидаю, что foo будет определено в foo.c, потому что его объявление находится в foo.h.

Есть ли другое место, кроме main. c ?

Да, в другой единице перевода - в другом .c файле. Например, в foo.c.

Если нет, то какой смысл использовать файлы заголовков?

Долгое время компьютеры go не были такими быстрые, как сегодня - но все же в этих компьютерах был C язык. Потому что компиляторы Histori c не могли проанализировать все определения функций в одном большом файле, потому что, например, из-за ограниченной памяти, доступной в то время, для скорости компиляции определение символа было разделено на более мелкие блоки. Объявление символов помещалось в один файл, определения - в другой. Таким образом, компиляторы компилируют небольшие .c файлы, которые видят только короткие .h файлы, содержащие только меньшее количество информации, а затем, после компиляции на отдельном этапе, компоновщик связывает все предварительно скомпилированные .c файлы вместе. Таким образом, компиляция использует меньше ресурсов за раз.

В настоящее время новым языкам программирования просто все равно - когда вы пишете на этих языках, например, import, компилятор работает так, как будто он извлекает объявления из определений. Компьютеры достаточно быстрые и мощные, чтобы справиться с этим с легкостью. С новейшим добавлением module s в C ++ сообщество C ++ надеется получить аналогичные механизмы.

Почему бы не определить (и таким образом объявить) функцию foo непосредственно в main. c?

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

2 голосов
/ 03 августа 2020

Функция printf объявлена ​​в stdio.h, однако я не определял функцию printf вручную. Почему код успешно компилируется? Разве это не должно вызывать ошибку компиляции, например, функция printf не определена?

Компилятор генерирует код, который вызывает printf. Он не проверяет, находится ли функция в других единицах компиляции или объектных файлах. Компилятору нужно только знать, что такое printf - ie, каков возвращаемый тип и параметры функции. Позже linker связывает объектные файлы и библиотеки и определяет, есть ли где-то printf. Поскольку вы ссылаетесь на стандартную библиотеку - скомпилированная функция printf присутствует, и связывание выполнено успешно. Если компоновщик не найдет функцию (или вообще объект) в объектных файлах и библиотеках, он выдаст ошибку.

Мой вопрос: где мне определить функцию foo? Есть ли другое место, кроме main. c?

Где угодно. В любом блоке компиляции. Важной частью является то, что сгенерированный объектный файл, содержащий foo код (определение), должен быть передан компоновщику.

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

Пример:

static void foo(int n);

int main(void)
{
    foo(123);
}

static void foo(int n)
{
    printf("%d\n", n);
}
2 голосов
/ 03 августа 2020

Основная цель файлов заголовков - объявить функции и переменные, определенные в одном модуле, для использования в другом несвязанном модуле.

Во втором примере вы можете создать foo. c со следующим содержимым :

int foo(int num)
{
    return num * 4;
}

Затем вы скомпилируете как foo. c, так и main. c:

gcc -c foo.c
gcc -c main.c

Затем свяжите их:

gcc -o my_program foo.o main.o

Если вы пытались скомпилировать и связать только main. c, вы получите ошибку компоновщика о том, что определение foo не может быть найдено.

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

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

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