Не нарушает ли переопределение функции из стандартной библиотеки правило единого определения?
#include <cmath>
double log(double) {return 1.0;}
int main() {
log(1.0);
}
Предположим, функция log()
в <cmath>
объявляется в глобальном пространстве имен (это на самом деле не определено, и мы просто делаем это предположение), затем оно ссылается на ту же функцию, что и log()
Функция, которую мы определили.
Таким образом, этот код нарушает правило с одним определением (см. Здесь, поскольку диагностика не требуется, этот код может скомпилироваться в каком-либо компиляторе, и мы не можем утверждать, что он верен)?
Примечание. После недавних изменений это не является дубликатом: что такое одно правило определения в C++?
3 ответа
Типичный сценарий.
Если extern "C" double log(double)
первоначально объявлен в глобальном пространстве имен, затем вы повторно объявили его и предоставили определение. Предыдущее упоминание о реализации extern "C"
переносит на ваше соответствующее объявление. Ваше определение относится к функции, относящейся к реализации, и это является нарушением ODR.
Что касается проявления UB: это, по-видимому, распространено log
как слабый символ линкера. Ваша реализация отменяет libc.so
согласно правилам ABI.
(Если реализация не делает extern "C"
, это все еще в основном все то же самое.)
Другой вероятный сценарий.
Если log
объявлен в namespace std
а затем перенести в глобальное пространство имен, тогда ваше объявление будет конфликтовать с ним. (На самом деле, using
Декларация технически декларация.) Эта ошибка диагностируется.
Предположительно нарушающий сценарий.
тогда это относится к той же функции, что и
log
Функция, которую мы определили
Один из способов реализации поставить <cmath>
имена в глобальном пространстве имен будет объявить extern "C"
функции внутри namespace std
тогда делай using namespace std
и убедиться, что это всегда происходит первым делом, когда включен любой стандартный заголовок. поскольку using namespace
не "прилипает" - он применяется только к предыдущим объявлениям в назначенном пространстве имен - остальная часть стандартной библиотеки не будет сделана видимой. (Это не будет объявлять имена в глобальном пространстве имен, но стандарт говорит только "помещен в глобальную область имен".)
В такой реализации ваше объявление скрыло бы стандартное и объявило бы новую функцию с новым искаженным именем (скажем, _Z3logd
вместо просто log
) и новое полное имя (::log
вместо ::std::log
). Тогда не было бы нарушения ODR (если только некоторая встроенная функция не использует log
в одном TU, а другой в другом TU).
Ниже рассматривается предыдущая версия OP. Я оставляю это здесь на случай, если будущие читатели придут сюда с похожим запросом.
Я предполагаю, что два имени ссылаются на одну и ту же сущность тогда и только тогда, когда они имеют одну и ту же декларативную область, где понятие "декларативная область" определено в стандарте [...] Является ли это предположение правильным? Есть ли в стандарте слово, поддерживающее это?
Это называется переменным сокрытием или в разговорной речи. И стандарт говорит то, что вы сказали, почти дословно. §3.3.10 №1 в текущем проекте стандарта C++17:
Имя может быть скрыто явным объявлением того же имени во вложенной декларативной области или производном классе
Таким образом, этот код нарушает правило с одним определением (см. Здесь, поскольку диагностика не требуется, этот код может скомпилироваться в каком-то компиляторе, и мы не можем утверждать, что это правильно)?
Я не буду этого ожидать. Стандарт требует, чтобы все cheader
заголовки (и в частности cmath
) ввести свои символы в std
Пространство имен. Реализация, которая также вливает его в глобальное пространство имен, соответствует стандарту (поскольку стандарт оставляет этот бит как неопределенный), но я бы нашел его в плохой форме. Вы правы, что это могло произойти. Теперь, если вы должны были включить math.h
(против мудрого совета), тогда это определенно приведет к нарушению правила одного определения.
Осторожно. ODR касается только определений, которые будут включены в итоговую программу. Это означает, что это не относится к символам, которые могут присутствовать в библиотеках, потому что (нормальный) компоновщик загружает не все библиотеки, а только те части, которые требуются для разрешения символов. Например, в этом коде:
#include <cmath>
double log(double) {return 1.0;}
int main()
{
log(1.0);
}
Там нет нарушения ODR:
- либо символ журнала из стандартной библиотеки C был включен только в
std
пространство имен и нет столкновения вообще - или он также включен в глобальное пространство имен
В последнем случае декларация double log(double)
не конфликтует с тем из cmath, потому что это то же самое. И как символ log
уже определено, его определение из стандартной библиотеки не будет включено в программу. Таким образом, только одно определение для log
В программе есть функция, которая: double log(double) {return 1.0;}
,
Все было бы иначе, если бы вы извлекли объектный модуль, содержащий log
из математической библиотеки и явно связать его в вашей программе. Потому что объектные модули всегда включаются в результирующую программу, тогда как объектные модули в библиотеках включаются только условно, если они разрешают неопределенные символы.
Рекомендации от стандарта:
Проекты n3337 для C++11 или n4296 для C++14 (или n4618 для последней ревизии) явно указаны в параграфе 2.2. Этапы перевода [lex.phases]:
§9. Все ссылки на внешние объекты разрешены. Компоненты библиотеки связаны для удовлетворения внешних ссылок на объекты, не определенные в текущем переводе. Весь такой вывод транслятора собирается в образ программы, который содержит информацию, необходимую для выполнения в среде выполнения.
Как показано в коде используется только одна единица перевода и как log
в нем уже определено, определение из библиотеки не будет использовано.