Почему существует одно правило определения в C / C ++ - PullRequest
4 голосов
/ 06 февраля 2020

В C и C ++ нельзя иметь функцию с двумя определениями. Например, скажем, у нас есть следующие два файла:

1.c:

int main(){ return 0}

2.c:

int main(){ return 0}

Выполнение команды gcc 1.c 2.c будет ошибка компоновщика duplicate symbol. Почему то же самое не происходит со структурами и классами? Почему нам разрешено иметь несколько определений одной и той же структуры, если они имеют одинаковые токены?

Ответы [ 6 ]

5 голосов
/ 06 февраля 2020

Чтобы ответить на этот вопрос, нужно углубиться в процесс компиляции и то, что нужно в каждой части (вопрос, почему эти шаги выполняются, более историчен, возвращаясь к началу C до его стандартизации)

* Программы 1055 * и C ++ компилируются в несколько этапов:

  1. Предварительная обработка
  2. Компиляция
  3. Связывание

Предварительная обработка - это все, что начинается с #, здесь это не очень важно.

Компиляция выполняется для каждой единицы перевода (обычно это один файл .c или .cpp плюс включенные в него заголовки). Компилятор берет по одному модулю перевода за раз, читает его и создает внутренний список классов и их членов, а затем код сборки каждой функции в данном модуле (на основе списка структур). Если вызов функции не является встроенным (например, он определен в другом TU), компилятор выдает «link» - «, пожалуйста, вставьте сюда функцию X », чтобы компоновщик прочитал.

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


Теперь, что нужно на каждом этапе?

Для этапа компиляции вам нужно

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

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


Подводя итог:

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

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

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

3 голосов
/ 06 февраля 2020

Нет варианта использования для функции с 2 определениями. Либо эти два определения должны быть одинаковыми, что делает их бесполезными, либо компилятор не сможет определить, какое из них вы имели в виду.

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

0 голосов
/ 06 февраля 2020

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

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

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

Что должно быть намерением использовать эту двусмысленность продуктивно? это вопрос, который вы на самом деле должны задать себе.

0 голосов
/ 06 февраля 2020

Фактически каждый элемент программирования связан с областью его применимости. И в этой области вы не можете иметь одно и то же имя, связанное с несколькими определениями элемента. В скомпилированном мире:

  1. Вы не можете иметь более одного определения класса с одним и тем же именем в одном файле. Но вы можете иметь его в разных единицах компиляции.
  2. Вы не можете иметь одну и ту же функцию или имя глобальной переменной в одном блоке ссылки (библиотека или исполняемый файл), но потенциально вы можете иметь функции с именем то же самое в разных библиотеках.
  3. вы не можете иметь общие библиотеки с одинаковым именем, расположенные в одном каталоге, но вы можете иметь их в разных каталогах.

Компиляция C / C ++ очень много после исполнения компиляции. Проверка 2 объектов, таких как функция или классы, на идентичность является трудоемкой задачей. Итак, это не сделано. Для сравнения рассматриваются только имена. Лучше учесть, что 2 типа различаются и выдают ошибки, чем проверять их на идентичность. Единственным исключением из этого правила являются текстовые макросы.

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

Типы являются концепцией компилятора, и они решаются компилятором. Типы не существуют в объектных библиотеках и представлены размерами соответствующих переменных. Таким образом, нет никакой причины для проверки коллизий имен типов в этой области.

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

И, как я уже упоминал, проверка функций на идентичное содержимое выполняется редко, однако есть некоторые случаи, но не в c или c ++.

0 голосов
/ 06 февраля 2020

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

Например, допустимо связать следующее вместе, потому что Понятно, какое определение struct Foo и какое определение f используется:

1.c:

struct Foo { int x; };
static void f(void) { struct Foo foo; ... }

2.c:

struct Foo { double d; };
static void f(void) { struct Foo foo; ... }
int main(void) { ... }

Но нельзя связать следующее вместе, потому что компоновщик не будет знать, на какой f позвонить.

1.c:

void f(void) { ... }

2.c:

void f(void) { ... }
int main(void) { f(); }
0 голосов
/ 06 февраля 2020

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

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

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

...