Разница в связи между C и С++?

Я прочитал существующие вопросы о внешней/внутренней связи здесь, на SO. Мой вопрос другой: что произойдет, если у меня есть несколько определений одной и той же переменной с внешней связью в разных единицах перевода в C и C++?

Например:

/*file1.c*/

typedef struct foo {
    int a;
    int b;
    int c;
} foo;

foo xyz;


/*file2.c*/

typedef struct abc {
    double x;
} foo;

foo xyz;

Используя Dev-С++ и как программу C, вышеуказанная программа компилирует и связывает отлично; тогда как он дает множественную ошибку переопределения, если она скомпилирована как программа на С++. Почему это должно работать под C и какая разница с С++? Это поведение undefined и зависит от компилятора? Как "плохо" это код и что мне делать, если я хочу его реорганизовать (я столкнулся с большим количеством старого кода, написанного так)?

Ответ 1

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

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

int a;

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

В вашем примере обе единицы перевода имеют (противоречивое) определение xyz из своих предварительных определений.

Ответ 2

Это вызвано изменением имени С++. Из Wikipedia:

Первые компиляторы С++ были реализованы как переводчики на источник C кода, который затем будет скомпилирован компилятор C для объектного кода; потому как из этого, имена символов должны были соответствовать к правилам идентификатора C. Даже позже, с появлением компиляторов, которые произведенный машинный код или сборка непосредственно, системный компоновщик как правило, не поддерживали символы С++, и все еще требовалось манипулирование.

Что касается compatibility:

Чтобы предоставить поставщикам компилятора большая свобода, стандарты С++ комитет решил не диктовать реализация манипулирования именами, обработки исключений и других особенности реализации. недостатком этого решения является то, что объектный код, созданный разными компиляторы, как ожидается, будут несовместимыми. Есть, однако, стандарты третьей стороны для конкретных машин или операционных систем, которые попытка стандартизировать компиляторы на эти платформы (например, С++ ABI [18]); некоторые компиляторы принимают вторичный стандарт для этих предметов.

С http://www.cs.indiana.edu/~welu/notes/node36.html приведен следующий пример:


Например, для кода ниже C

int foo(double*);
double bar(int, double*);

int foo (double* d) 
{
    return 1;
}

double bar (int i, double* d) 
{
    return 0.9;
}

Его таблица символов будет (через dump -t)

[4]  0x18        44       2     1   0   0x2 bar
[5]  0x0         24       2     1   0   0x2 foo

Для того же файла, если компилировать в g++, тогда таблица символов будет

[4]  0x0         24       2     1   0   0x2 _Z3fooPd
[5]  0x18        44       2     1   0   0x2 _Z3bariPd

_Z3bariPd означает функцию, имя которой является баром и чей первый arg является целым, а второй аргумент - двойным.


Ответ 3

С++ не позволяет определять символ более одного раза. Не уверен, что делает C-компоновщик, можно предположить, что он просто отображает оба определения на один и тот же символ, что, конечно, вызовет серьезные ошибки.

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

Ответ 4

Программа C разрешает это и обрабатывает память немного как объединение. Он будет работать, но может не дать вам то, что вы ожидали.

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

Ответ 5

Вы нашли Одно правило определения. Очевидно, что ваша программа имеет ошибку, так как

  • После подключения программы может быть только один объект с именем foo.
  • Если какой-либо исходный файл содержит все файлы заголовков, он увидит два определения foo.

Компиляторы С++ могут перемещаться вокруг # 1 из-за "имени mangling": имя вашей переменной в связанной программе может отличаться от выбранной вами. В этом случае это не требуется, но вероятно, как ваш компилятор обнаружил проблему. # 2, однако, остается, поэтому вы не можете этого сделать.

Если вы действительно хотите победить механизм безопасности, вы можете отключить mangling следующим образом:

extern "C" struct abc foo;

... другой файл...

extern "C" struct foo foo;

extern "C" инструктирует компоновщик использовать соглашения C ABI.