2010-01-08 3 views
10

Я прочитал существующие вопросы по внешним/внутренним связям здесь, на SO. Мой вопрос другой: что произойдет, если у меня есть несколько определений той же переменной с внешней связью в разных единицах перевода под C и C++?Разница в связи между 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, выше программа компилируется и идеально; тогда как он дает множественную ошибку переопределения, если она скомпилирована как программа на C++. Почему он должен работать под C и какова разница с C++? Является ли это поведение неопределенным и зависит от компилятора? Как «плохо» этот код и что мне делать, если я хочу его реорганизовать (я столкнулся с большим количеством старого кода, написанного так)?

ответ

4

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

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

int a; 

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

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

0

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

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

+1

Поведение C может быть верным для вашей реализации, но язык не гарантируется. –

+0

Имя переменной - это всего лишь метка для адреса памяти. Если вы даете два определения того, как интерпретировать этот ярлык, это не делает волшебным образом ярлык для обозначения двух разных объектов. Вы когда-нибудь видели компоновщика, который будет вести себя по-другому? –

+0

Я не отрицаю, что это обычное поведение компоновщика, это поведение используется другими языками и многими реализациями C. Однако следствие вашего ответа состояло в том, что это четко определенное поведение. Разрешить более одного внешнего определения в программе является общим расширением, согласно стандарту C стандарта J, но даже с этим расширением, если определения не согласуются, это приводит к неопределенному поведению. –

1

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

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

+0

Уверен, его можно определить более одного раза. Однако определения должны быть идентичными. – Potatoswatter

+1

@Potatoswatter: объекты должны быть _defined_ только один раз, они могут быть _declared_ несколько раз. Функции 'inline' являются особенными, поскольку они могут быть определены один раз для единицы перевода, но другие функции должны быть определены только один раз в каждой программе. –

+0

Извините, мой плохой: P – Potatoswatter

2

Это связано с изменением имени C++. От Wikipedia:

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

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

Для того, чтобы дать поставщикам компилятором большую свободу, C++ стандартов комитет решил не диктовать реализацию имя коверкая, обработки исключений и другие особенности реализации. Недостатком этого решения является , что объектный код, составленный различными компиляторами , должен быть несовместим. Существуют, однако, сторонние стандарты для конкретных машин или операционных систем, которые пытаются стандартизировать компиляторы на этих платформах (например, C++ 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 

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

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

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


0

Вы нашли One Definition Rule. Очевидно, что ваша программа имеет ошибку, поскольку

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

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

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

extern "C" struct abc foo; 

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

extern "C" struct foo foo; 

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

+0

О, конечно, как кто-то еще упомянул, вы должны просто использовать «союз». – Potatoswatter

Смежные вопросы