Определение структуры и функции в области видимости
Итак, насколько я знаю, это законно в C:
foo.c
struct foo {
int a;
};
bar.c
struct foo {
char a;
};
Но то же самое с функциями является незаконным:
foo.c
int foo() {
return 1;
}
bar.c
int foo() {
return 0;
}
и приведет к связыванию ошибки (множественное определение функции foo
).
Это почему? Какая разница между именами структур и именами функций, которые не позволяют C обрабатывать один, но не другой? Также это поведение распространяется на C++?
Ответы
Ответ 1
Это почему?
struct foo {
int a;
};
определяет шаблон для создания объектов. Он не создает никаких объектов или функций. Если struct foo
не используется где-то в вашем коде, что касается компилятора/компоновщика, эти строки кода также могут не существовать.
Обратите внимание, что существует различие в том, как C и C++ имеют дело с несовместимыми определениями struct
.
Различные определения struct foo
в вашем опубликованном коде, хорошо в программе C, если вы не смешиваете их использование.
Однако это не является законным в C++. В C++ они имеют внешнюю связь и должны быть определены одинаково. См. 3.2 Одно правило определения /5 для получения дополнительной информации.
Ответ 2
Отличительная концепция в этом случае называется связью.
В строках C, union или enum нет привязки. Они эффективно локальны по своим масштабам.
6.2.2 Связи идентификаторов
6 Следующие идентификаторы не имеют привязки: идентификатор, объявленный как нечто, отличное от объекта или функции; идентификатор, объявленный как параметр функции; идентификатор области блока для объекта, объявленного без спецификатора класса хранения extern
.
Они не могут быть повторно объявлены в той же области (за исключением так называемых форвардных объявлений). Но они могут быть свободно повторно объявлены в разных областях, включая разные единицы перевода. В разных областях они могут объявлять полностью независимые типы. Это то, что у вас есть в вашем примере: в двух разных единицах перевода (т.е. В двух разных областях файлов) вы объявили два разных и несвязанных типа struct foo
. Это совершенно законно.
Между тем функции имеют связь в C. В вашем примере эти два определения определяют ту же функцию foo
с внешней связью. И вам не разрешено предоставлять более одного определения любой внешней функции привязки во всей программе
6.9 Внешние определения
5 [...] Если идентификатор, объявленный с внешней связью, используется в выражении (кроме как в части операнда оператора sizeof
или _Alignof
, результат которого является целочисленной константой), где-то во всей программе должно быть ровно одно внешнее определение для идентификатора; в противном случае должно быть не более одного.
В C++ концепция связи расширяется: она назначает конкретную связь с гораздо более широким кругом сущностей, включая типы. В C++ типы классов имеют связь. Классы, объявленные в области пространства имен, имеют внешнюю связь. И в одном правиле определения C++ явно указано, что если класс с внешней связью имеет несколько определений (через разные единицы перевода), он должен быть определен эквивалентно во всех этих единицах перевода (http://eel.is/C++draft/basic.def.odr#12). Таким образом, в C++ ваши определения struct
будут незаконными.
Определения ваших функций остаются незаконными в C++, а также из-за правила ODR C++ (но по существу по тем же причинам, что и в C).
Ответ 3
Определения функций объявляют объект с именем foo
с внешней связью, а в стандарте C указывается, что не должно быть более одного определения объекта с внешней связью. Определенные типы структур не являются объектами с внешней связью, поэтому вы можете иметь более одного определения struct foo
.
Если вы объявили объекты с внешней связью, используя одно и то же имя, это будет ошибкой:
foo.c
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
struct foo obj;
Теперь у вас есть два объекта, называемых obj
которые имеют внешнюю связь, что запрещено.
Это было бы неправильно, даже если один из объектов объявлен, а не определен:
foo.c
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
extern struct foo obj;
Это не определено, потому что два объявления obj
относятся к одному и тому же объекту, но у них нет совместимых типов (поскольку struct foo
определяется по-разному в каждом файле).
C++ имеет похожие, но более сложные правила, для учета inline
функций и inline
переменных, шаблонов и других функций C++. В C++ соответствующие требования известны как правило с одним определением (или ODR). Одна заметная разница заключается в том, что C++ даже не допускает двух разных описаний struct
, даже если они никогда не используются для объявления объектов с внешней связью или иначе "разделяемых" между единицами перевода.
Ответ 4
Два объявления для struct foo
несовместимы друг с другом, поскольку типы членов не совпадают. Использование их как внутри каждой единицы перевода прекрасно, если вы не делаете ничего, чтобы сбить их с толку.
Если, например, вы сделали это:
foo.c:
struct foo {
char a;
};
void bar_func(struct foo *f);
void foo_func()
{
struct foo f;
bar_func(&f);
}
bar.c:
struct foo {
int a;
};
void bar_func(struct foo *f)
{
f.a = 1000;
}
Вы будете ссылаться на неопределенное поведение, потому что struct foo
, bar_func
ожидает bar_func
несовместима со struct foo
которую предоставляет foo_func
.
Совместимость структур подробно описана в разделе 6.2.7 стандарта C:
1 Два типа имеют совместимый тип, если их типы одинаковы. Дополнительные правила для определения совместимости двух типов описаны в 6.7.2 для спецификаторов типов, в 6.7.3 для классификаторов типов и в 6.7.6 для деклараторов. Более того, два типа структуры, объединения или перечисления, объявленные в отдельных единицах перевода, совместимы, если их теги и члены удовлетворяют следующим требованиям: если объявлено тегом, другое объявляется с тем же тегом. Если оба они заполнены в любом месте своих соответствующих единиц перевода, применяются следующие дополнительные требования: между их членами должно быть взаимно однозначное соответствие, чтобы каждая пара соответствующих членов была объявлена с совместимыми типами; если один член пары объявлен с помощью спецификатора выравнивания, другой объявлен с эквивалентным спецификатором выравнивания; и если один член пары объявлен с именем, другой объявляется с тем же именем. Для двух структур соответствующие члены должны быть объявлены в том же порядке. Для двух структур или объединений соответствующие битовые поля должны иметь одинаковую ширину. Для двух перечислений соответствующие члены должны иметь одинаковые значения.
2 Все объявления, относящиеся к одному объекту или функции, должны иметь совместимый тип; в противном случае поведение не определено.
Итак, два экземпляра struct foo
должны иметь члены с тем же именем и типом и в том же порядке, чтобы быть совместимыми.
Такие правила необходимы, так что struct
может быть определена один раз в файле заголовка, и этот заголовок впоследствии будет включен в несколько исходных файлов. Это приводит к тому, что struct
определяется в нескольких исходных файлах, но при этом каждый экземпляр является совместимым.
Ответ 5
Разница заключается не столько в именах, сколько в существовании; определение структуры не хранится нигде, и его имя существует только во время компиляции.
(Ответственность программиста заключается в том, чтобы не допускать конфликтов в использовании идентично названных структур. В противном случае вызывается наш дорогой старый друг Undefined Behavior.)
С другой стороны, функция должна быть где-то сохранена, и если она имеет внешнюю связь, линкер нуждается в ее имени.
Если вы сделаете свои функции static
, поэтому они "невидимы" вне их соответствующего блока компиляции, ошибка связывания исчезнет.
Ответ 6
Чтобы скрыть определение функции из компоновщика, используйте ключевое слово static.
foo.c
static int foo() {
return 1;
}
bar.c
static int foo() {
return 0;
}