Согласно стандарту c ++:
Ни одна единица перевода не должна содержать более одного определения любого
переменная, функция, тип класса, тип перечисления или шаблон.
//--translation_unit.cpp--//
int a;
void foo()
{
int a; //Second defention of a. ODR fails.
}
Можете ли вы объяснить мне, как на самом деле работает ODR?
Это не нарушает правила, потому что вы определяете две разные переменные. Они имеют одинаковые имена, но объявляются в разных областях, и поэтому являются отдельными объектами. У каждого есть одно определение.
Объявление в области действия функции называется скрывать один в глобальном пространстве имен. В рамках функции неквалифицированное имя a
относится к локальной переменной, в то время как квалифицированное имя ::a
относится к глобальному.
Вы не определили a
снова.
Вы только что определили новую переменную a
, Он имеет область видимости только внутри функции и не имеет ничего общего с исходной областью (которая имеет глобальную область видимости) и скрывает исходную область внутри функции.
Они не нарушают ODR, потому что имеют различную сферу применения.
Первый a
имеет глобальный охват
Известна переменная с глобальной областью действия (также называемая областью файлов)
по всему файлу после точки, где он определен
Второй a
имеет локальная сфера
Известна переменная с локальной областью действия (также называемая блочной областью действия)
только в пределах блока, в котором он определен
Для более ясного понимания ODR в C ++ необходимо изучить следующие концепции: Продолжительность хранения, объем и связь
Можете ли вы объяснить мне, как на самом деле работает ODR?
Вот пример нарушения ODR:
/* file : module.cpp */
#include <stdio.h>
inline int foo() {
printf("module.foo: %p\n", &foo);
return 1;
}
static int bar = foo();
/* file : main.cpp */
#include <stdio.h>
inline int foo() {
printf("main.foo: %p\n", &foo);
return 2;
}
int main(int argc, char *argv[]) {
return foo();
}
Как видите, функция int foo()
определяется по-разному в двух модулях. Теперь посмотрим, как это приводит к разному поведению в зависимости от запрошенного уровня оптимизации (O3 против O0):
$ clang++ --std=c++11 -O0 main.cpp module.cpp && ./a.out
module.foo: 0x100a4aef0
module.foo: 0x100a4aef0
$ clang++ --std=c++11 -O3 main.cpp module.cpp && ./a.out
module.foo: 0x101146ee0
main.foo: 0x101146ee0
Выходные данные различны, поскольку для встроенных функций компилятор создает символ компоновщика в каждом модуле компиляции. Этот символ (в каждом модуле компиляции) помечен как «выберите любой, они все одинаковые». В первом случае, когда все оптимизации отключены, компоновщик выбирает определение из module.cpp. Во втором случае компилятор просто включает обе функции, поэтому никакой дополнительной работы с компоновщиком не требуется.
Есть и другие примеры, когда нарушение ODR вызывает странное поведение. Так что не делай этого 🙂
Постскриптум Бонус, случай из реальной жизни:
/* a.cpp */
struct Rect
{
int x,y,w,h;
Rect(): x(0),y(0),w(0),h(0)
};
/* b.cpp */
struct Rect
{
double x,y,w,h;
Rect(): x(0),y(0),w(0),h(0)
};
Проблема здесь та же, что и в предыдущем примере (поскольку конструкторы Rect неявно встроены). В зависимости от фазы луны компилятор выбирал ту или иную реализацию, получая странные результаты (int
версия оставит часть doubles
неинициализированным, double
версия выйдет за пределы int
и испорченная память там). Хороший способ защититься от этого — использовать анонимные пространства имен (C ++) или объявить struct as static
(С):
/* file.cpp */
namespace {
struct Rect { ... };
}
/* file.c */
static struct Rect { ... };