Определение структуры и функции в области видимости

Итак, насколько я знаю, это законно в 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;
    }