Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[44.192.95.161] |
|
Сообщ.
#1
,
|
|
|
Visual Studio 2022, C++20.
#include <iostream> float exp = 1.0; //Error C2365 'exp': redefinition; previous definition was 'function' int main(){ std::cout<<exp<<std::endl; return 0; } Поражает, что я не подключаю cmath и т.п., не подключаю пространство имён std. И всё равно возникает такая херня! Я хочу использовать имя "exp" не отказываясь от подключения заголовочного файла <iostream> и не помещая своё exp в какое-либо пространство имён. |
Сообщ.
#2
,
|
|
|
А exp это разве не встроенная функция. Она должна быть доступна вообще без подключения модулей. Наличие #include <iostream> тут не причем.
|
Сообщ.
#3
,
|
|
|
Цитата macomics @ А exp это разве не встроенная функция. Она должна быть доступна вообще без подключения модулей. Наличие #include <iostream> тут не причем Нет. Это библиотечная функция (стандартная библиотека) из модуля (заголовочного файла) про математику. И для меня странно её попадание в код посредством заголовочного файла для ввода-вывода, и еще страннее её доступность без пространства имён std. Зачем такое делать??? |
Сообщ.
#4
,
|
|
|
В библиотеке потоков C++98 был дефект, заключающийся в том, что подключив <iostream>, мы лишь получаем доступ к стандартным потокам, и по сути можем лишь... получить ссылку, например. Элементарный operator<< по-хорошему был недоступен, т.к. он определён в другом заголовке. Т.ч. в дополнение к <iostream> приходилось подключать ещё всякие там <istream> и иже с ним. В C++11 этот дефект был исправлен, т.к. Комитет решил, что <iostream> без этих дополнительных заголовков практически бесполезен, т.ч. теперь стандартизировано, что он включает <istream>, <ostream>, <ios> и <streambuf>. VS именно так и поступает (пусть и несколько окольным путём, как я не поленился посмотреть), но она идёт также немного дальше.
<streambuf> определяет std::basic_streambuf<>, а у него регламентируется поддержка локалей через std::basic_streambuf<>::imbue(). Также Стандарт регламентирует, что не только все методы фасетов константны, но и std::use_facet<> возвращает константные ссылки на фасеты. Стандарт это делает, чтобы гарантировать неизменность национальных особенностей в std::locale после создания его экземпляра, что в общем-то более чем резонно. У потоков в итоге есть прекрасное средство оптимизации: ещё внутри std::basic_streambuf<>::imbue() они могут выполнить все необходимые подготовительные операции для подстройки под новые пользовательские соглашения и предпочтения. Поэтому стандартная библиотека в VS в <streambuf> тянет кучу других заголовков, и т.к. все (почти) фасеты являются шаблонами, ибо зависят от типа символа, которым конкретизируются, char там, или wchar_t, или ещё каким, то и их методы лежат в заголовках (как того требует правила работы с инстанцированием шаблонов вообще и ODR в частности). В итоге в эту кучу входит <cmath>, который нужен std::num_put<> (а может и std::num_get<>). Так <cmath> оказывается косвенно включённым в единицу трансляции, которая подключает <iostream>. Это был первый вопрос "как так-то?". Вопрос второй "а имела ли право VS так поступать". Я пересмотрел Стандарты C++ и C и не обнаружил там ни одного запрета на то, чтобы какой-нибудь стандартный заголовок включал другой заголовок, также из числа стандартных. Так что ответ "да, имела". Вопрос третий "а насколько это хорошо" уже далеко не так очевиден. Спервоначалу следует заметить, что Стандарты в один голос утверждают, что использование библиотечных сущностей без предварительного подключения соответствующего заголовка ведёт к неопределённому поведению. Формально символ exp относится к библиотеке C, а не С++, однако библиотека C является частью С++, так что по-любому присутствует в языке и наследует все особенности использования. (При этом символы из глобальной области видимости дублируются в std.) Твой код использует символ exp, но не подключает заголовок <cmath>, в итоге ты имеешь то, что видишь: одно из возможных проявлений неопределённого поведения. Твоё желание использовать именно exp понятно, я сам когда-то ловил грабли с exit, но в итоге плюнул и переименовал её в quit. Не могу не согласиться ни с компилятором, ни со Стандартом, ибо просто взглянув на твой код, я тоже словил "так, стоп" и только через пару секунд сообразил, что ты хотел написать, куда уж тут бездушным кремниевым болванкам. Не ну и правда, что такое exp я знаю, отсутствие подключения <cmath> ещё заметить надо, и заметив, непонятно, где баг: программер то ли набажил с функцией, то ли с заголовком, то ли с присваиванием, то ли вообще не набажил. Я бы даже сказал, что VS правильно поступила, что выругалась ещё при компиляции, предупредив неопределённое поведение. Если б он косвенно не подключил <cmath> при компиляции, могло получиться так, чтоб на множественное определение exp мог выругаться линкер, т.к. предкомпилированная exp() уже лежит в где-нибудь в glibc++.a и тянется вместе с каким-нибуль operator<<. А мог и не выругаться, т.к. это банальное нарушение ODR, для которого не требуется диагностики, в итоге полгода нормально работавшая программа вдруг начинает падать на ровном месте, когда exp понадобилось присвоить новое значение. Мораль в целом вот: не стоит использовать Стандартные идентификаторы для своих целей. Добавлено P.S. И да – все символы стандартной библиотеки C находятся в глобальном пространстве имён и в C++ тоже. Причём для этого есть и более веская причина, нежели легаси-код или там удобство. Причина в ADL, без которой нынешний C++ просто не существовал бы, и который пришлось внести в язык ещё в середине 90-ых, т.е. до первого стандарта C++98. |
Сообщ.
#5
,
|
|
|
Цитата Qraizer @ В библиотеке потоков C++98 был дефект, заключающийся в том, что подключив <iostream>, мы лишь получаем доступ к стандартным потокам, и по сути можем лишь... получить ссылку, например. Элементарный operator<< по-хорошему был недоступен, т.к. он определён в другом заголовке. Т.ч. в дополнение к <iostream> приходилось подключать ещё всякие там <istream> и иже с ним. В C++11 этот дефект был исправлен, т.к. Комитет решил, что <iostream> без этих дополнительных заголовков практически бесполезен, т.ч. теперь стандартизировано, что он включает <istream>, <ostream>, <ios> и <streambuf>. VS именно так и поступает (пусть и несколько окольным путём, как я не поленился посмотреть), но она идёт также немного дальше. <streambuf> определяет std::basic_streambuf<>, а у него регламентируется поддержка локалей через std::basic_streambuf<>::imbue(). Также Стандарт регламентирует, что не только все методы фасетов константны, но и std::use_facet<> возвращает константные ссылки на фасеты. Стандарт это делает, чтобы гарантировать неизменность национальных особенностей в std::locale после создания его экземпляра, что в общем-то более чем резонно. У потоков в итоге есть прекрасное средство оптимизации: ещё внутри std::basic_streambuf<>::imbue() они могут выполнить все необходимые подготовительные операции для подстройки под новые пользовательские соглашения и предпочтения. Поэтому стандартная библиотека в VS в <streambuf> тянет кучу других заголовков, и т.к. все (почти) фасеты являются шаблонами, ибо зависят от типа символа, которым конкретизируются, char там, или wchar_t, или ещё каким, то и их методы лежат в заголовках (как того требует правила работы с инстанцированием шаблонов вообще и ODR в частности). В итоге в эту кучу входит <cmath>, который нужен std::num_put<> (а может и std::num_get<>). Так <cmath> оказывается косвенно включённым в единицу трансляции, которая подключает <iostream>. Это был первый вопрос "как так-то?". Вопрос второй "а имела ли право VS так поступать". Я пересмотрел Стандарты C++ и C и не обнаружил там ни одного запрета на то, чтобы какой-нибудь стандартный заголовок включал другой заголовок, также из числа стандартных. Так что ответ "да, имела". Вопрос третий "а насколько это хорошо" уже далеко не так очевиден. Спервоначалу следует заметить, что Стандарты в один голос утверждают, что использование библиотечных сущностей без предварительного подключения соответствующего заголовка ведёт к неопределённому поведению. Формально символ exp относится к библиотеке C, а не С++, однако библиотека C является частью С++, так что по-любому присутствует в языке и наследует все особенности использования. (При этом символы из глобальной области видимости дублируются в std.) Твой код использует символ exp, но не подключает заголовок <cmath>, в итоге ты имеешь то, что видишь: одно из возможных проявлений неопределённого поведения. Твоё желание использовать именно exp понятно, я сам когда-то ловил грабли с exit, но в итоге плюнул и переименовал её в quit. Не могу не согласиться ни с компилятором, ни со Стандартом, ибо просто взглянув на твой код, я тоже словил "так, стоп" и только через пару секунд сообразил, что ты хотел написать, куда уж тут бездушным кремниевым болванкам. Не ну и правда, что такое exp я знаю, отсутствие подключения <cmath> ещё заметить надо, и заметив, непонятно, где баг: программер то ли набажил с функцией, то ли с заголовком, то ли с присваиванием, то ли вообще не набажил. Я бы даже сказал, что VS правильно поступила, что выругалась ещё при компиляции, предупредив неопределённое поведение. Если б он косвенно не подключил <cmath> при компиляции, могло получиться так, чтоб на множественное определение exp мог выругаться линкер, т.к. предкомпилированная exp() уже лежит в где-нибудь в glibc++.a и тянется вместе с каким-нибуль operator<<. А мог и не выругаться, т.к. это банальное нарушение ODR, для которого не требуется диагностики, в итоге полгода нормально работавшая программа вдруг начинает падать на ровном месте, когда exp понадобилось присвоить новое значение. Мораль в целом вот: не стоит использовать Стандартные идентификаторы для своих целей. Добавлено Сегодня, 17:58 P.S. И да – все символы стандартной библиотеки C находятся в глобальном пространстве имён и в C++ тоже. Причём для этого есть и более веская причина, нежели легаси-код или там удобство. Причина в ADL, без которой нынешний C++ просто не существовал бы, и который пришлось внести в язык ещё в середине 90-ых, т.е. до первого стандарта C++98. Спасибо за подробный ответ. |
Сообщ.
#6
,
|
|
|
Тогда тут получается ругается линкер?
int exp = 1; int main(){ return exp; } $ g++ -o c1.o -c 1.cpp 1.cpp:1:5: предупреждение: built-in function «exp» declared as non-function [-Wbuiltin-declaration-mismatch] 1 | int exp = 1; | ^~~ ADD: Хотя, подозреваю, что это gcc так перестраховывается, чтобы предупредить появление подобных ошибок. |
Сообщ.
#7
,
|
|
|
Нет, конечно. Компилятор. Когда Стандарт на нарушение говорит, мол, диагностики не требуется, это не означает, что её не должно быть, это означает лишь, что она может отсутствовать.
Я проверил на g++ под убундой, там всё нормально и компилится, и собирается. Типичный пример неопределённого поведения, в разных реализациях может быть по-разному. В частности и ожидаемым образом, как у меня. Но. Чуть изменим ситуацию: // f1.cpp #include <iostream> float exp = 1.0; float foo(float); int main() { std::cout << exp << '\t' << foo(exp) << std::endl; return 0; } // f2.cpp #include <cmath> float foo(float x) { return exp(x); } censored:~/Документы/c++$ g++ f1.cpp f2.cpp censored:~/Документы/c++$ ./a.out Ошибка сегментирования (стек памяти сброшен на диск) censored:~/Документы/c++$ // f2.cpp #include <cmath> float foo(float x) { return std::exp(x); } censored:~/Документы/c++$ g++ f1.cpp f2.cpp censored:~/Документы/c++$ ./a.out 1 2.71828 censored:~/Документы/c++$ |