Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>В обсуждаемой ситуации, когда указатель взаимозаменяем со ссылкой, нет никакого смысла его проверять, кроме как в assert'е, который должен ставиться до разыменования.
Если прямо взаимозаменяемый — то согласен.
ЕМ>А assert точно так же имеет смысл использовать и для проверки ссылок, переданных сверху и передаваемых дальше вниз, чтобы как можно раньше поймать ошибку.
Не имеет. К ссылке заранее уровень доверия — "бог".
К>>
К>>void p(C* c) {
К>> B* b = c; // b = c ? (c со смещением базы) : nullptr
К>>}
К>>void r(C& c) {
К>> B& b = c; // &b = &c ? (&c со смещением базы) : (голое смещение базы)
К>>
ЕМ>Какие компиляторы так делают? MS VC++ уже минимум двадцать лет дает одинаковую арифметику что для указателей, что для ссылок.
На, полюбуйся, транк гцц
https://gcc.godbolt.org/z/xcd36fedo
ЕМ>Но дело даже не в этом, а в том, что без явных проверок программа все равно грохнется. Так какой смысл вникать в тонкости?
Она может грохнуться по-разному. Или НЕ грохнуться, и тоже по-разному.
Неопределённое поведение тем и интересно.
Вот пример программы, которая не валится.
#include <cstdio>
#include <cstdint>
struct A { int x; };
struct B { int y; };
struct C : A, B {};
template<class T> void foo(C* p) {
T* x = ((T*)p);
T* y = (&(T&)*p);
printf("%p\n", x);
printf("%p\n", y);
printf("%s\n\n", x==y ? "same" : "different");
}
int main() {
C* p = nullptr;
foo<C>(p);
foo<A>(p);
foo<B>(p);
}
Вместо printf мог стоять любой код, чувствительный к nullptr.
Мы надеемся, что нам всё равно — непосредственный статик-каст или через разыменование и обратно. Вроде ведь одно и то же написано?
И код совсем-совсем одинаковый... почему же он не взаимозаменяемый, а?
Здравствуйте, Кодт, Вы писали:
К>К ссылке заранее уровень доверия — "бог".
Только у тех, кто полагает, будто программа исполняется за счет магии. Ну, или кому просто везет. Кому хоть раз довелось часами-сутками-неделями искать причину случайной порчи памяти, после этого навсегда понимают, что технически ссылка не отличается от любой другой переменной, и чем раньше будет поймано несоответствие, тем меньше времени и нервов придется потратить.
К>Вот пример программы, которая не валится.
Исключительно потому, что работает лишь с адресами, а не указываемыми объектами.
К>И код совсем-совсем одинаковый... почему же он не взаимозаменяемый, а?
Может, потому, что в нем изначально была поставлена цель не решить практическую задачу (о чем идет речь в теме), а продемонстрировать (непонятно, зачем) особенности реализации?
Здравствуйте, T4r4sB, Вы писали:
C>>причём, в режиме -O3 инструкции идентичны:
TB>Потому что ты написал не то. В случае со std::string надо передавать std::string_view, а в случае с std::unique_ptr надо передавать указатель на внутренний ресурс при помощи get()
std::string для примера был. А вопрос как раза про то, почему используют get() вместо передачи по ссылке?
Здравствуйте, cppguard, Вы писали:
C>std::string для примера был. А вопрос как раза про то, почему используют get() вместо передачи по ссылке?
Потому что юник — это уже обёртка над указателем. Если ты передаёшь юник по ссылке, то ты по сути передаёшь ссылку на указатель. Это двойная индирекция, ну говно же.
К тому же когда ты на вход берёшь указатель, ты не прибиваешь пользователя гвоздями к тому, как именно получен этот указатель — как элемент юника, или как указатель на поле какой-то структуры, или указатель на переменную на стеке, больше гибкости.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Может, потому, что в нем изначально была поставлена цель не решить практическую задачу (о чем идет речь в теме), а продемонстрировать (непонятно, зачем) особенности реализации?
Прирешении практических задач подобные мелкие моменты будут возникать регулярно, пусть и в более завуалированном виде.
Здравствуйте, T4r4sB, Вы писали:
TB>Потому что юник — это уже обёртка над указателем. Если ты передаёшь юник по ссылке, то ты по сути передаёшь ссылку на указатель. Это двойная индирекция, ну говно же. TB>К тому же когда ты на вход берёшь указатель, ты не прибиваешь пользователя гвоздями к тому, как именно получен этот указатель — как элемент юника, или как указатель на поле какой-то структуры, или указатель на переменную на стеке, больше гибкости.
OMG При чём тут вообще ссылка на unique_ptr? Я же код привёл даже. Речь идёт о ссылке на значение, хранимое внутри unique_ptr.
Здравствуйте, T4r4sB, Вы писали:
TB>Прирешении практических задач подобные мелкие моменты будут возникать регулярно, пусть и в более завуалированном виде.
Можно примеров, иллюстрирующих регулярность? Так, чтоб указатели/ссылки использовались исключительно в целях косвенной адресации, но чтоб там возникала объективная потребность в работе именно с самим указателем, а не указываемым объектом.
Здравствуйте, so5team, Вы писали:
S>И как вы предлагаете проверять "несоответствия" для ссылок? Технически.
Так же, как и для указателей. Например, если адрес всегда должен быть выровнен, достаточно проверить само значение. Если известно, что объект всегда выделяется в куче — вызвать соответствующую функцию, и так далее.
Здравствуйте, Евгений Музыченко, Вы писали:
S>>И как вы предлагаете проверять "несоответствия" для ссылок? Технически.
ЕМ>Так же, как и для указателей.
Валидный указатель можно сравнивать с nullptr. Как вы предлагаете сравнивать с nullptr ссылку?
ЕМ>Например, если адрес всегда должен быть выровнен, достаточно проверить само значение.
Если мне не изменяет склероз, то разговор начался с того, что вы стали защищать тезис о том, что ссылку можно получить и для нулевого указателя. И не захотели согласиться с тем, что такие случаи рассматриваться не должны, т.к. это явная ошибка.
Могли бы вы рассказать, какое отношение проверка правильности выравнивания объекта в памяти имеет к исходному тезису "Ссылка тоже может быть нулевой, если создана из нулевого указателя."?
Здравствуйте, so5team, Вы писали:
S>Как вы предлагаете сравнивать с nullptr ссылку?
(&ref != nullptr)
S>Если мне не изменяет склероз, то разговор начался с того, что вы стали защищать тезис о том, что ссылку можно получить и для нулевого указателя.
Изначально я защищал тезис о том, что между ссылками и указателями нет принципиальной разницы, если логика работы программы не зависит от конкретных значений адресов (не использует явно дополнительную информацию, которую может нести адрес).
S>И не захотели согласиться с тем, что такие случаи рассматриваться не должны, т.к. это явная ошибка.
Если это делается умышленно — да, ошибка явная. Но это может произойти и неумышленно.
S>Могли бы вы рассказать, какое отношение проверка правильности выравнивания объекта в памяти имеет к исходному тезису "Ссылка тоже может быть нулевой, если создана из нулевого указателя."?
Самое непосредственное. Если ссылка создается путем преобразования указателя, всегда есть вероятность того, что он может оказаться и нулевым, и невалидным. Даже если ссылка создается прямиком из статического объекта, она не перестает быть неявным указателем, который может быть испорчен при банальном переполнении буфера, не говоря уже о малозаметных ошибках в адресной арифметике. Если об этом никогда не думать, считая любую ссылку валидной по определению, можно потратить много времени на поиски ошибок вовсе не там, где их следует искать.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Здравствуйте, so5team, Вы писали:
S>>Как вы предлагаете сравнивать с nullptr ссылку?
ЕМ>(&ref != nullptr)
Если я понимаю правильно, то когда у нас есть что-то вроде:
A * ptr = nullptr;
A & ref = *ptr; // (1)if( &ref != nullptr ) // (2)
то в точке (1) мы имеем UB из-за которого код в точке (2) невалиден в принципе.
Т.е. вы не может сравнить ссылку, полученную из нулевого указателя, ни с чем. Т.к. этой самой ссылки у вас в наличии нет. Есть какой-то мусор, которым оперировать нельзя.
S>>Если мне не изменяет склероз, то разговор начался с того, что вы стали защищать тезис о том, что ссылку можно получить и для нулевого указателя.
ЕМ>Изначально я защищал тезис о том, что между ссылками и указателями нет принципиальной разницы, если логика работы программы не зависит от конкретных значений адресов (не использует явно дополнительную информацию, которую может нести адрес).
Как-то из утверждения "Ссылка тоже может быть нулевой, если создана из нулевого указателя." этого не следовало, имхо.
S>>Могли бы вы рассказать, какое отношение проверка правильности выравнивания объекта в памяти имеет к исходному тезису "Ссылка тоже может быть нулевой, если создана из нулевого указателя."?
ЕМ>Самое непосредственное. Если ссылка создается путем преобразования указателя, всегда есть вероятность того, что он может оказаться и нулевым, и невалидным.
Да.
ЕМ>Даже если ссылка создается прямиком из статического объекта, она не перестает быть неявным указателем
А это уже какой-то внезапный перескок в совсем другую область.
ЕМ>Если об этом никогда не думать, считая любую ссылку валидной по определению, можно потратить много времени на поиски ошибок вовсе не там, где их следует искать.
Я вам больше скажу: любой указатель можно считать валидным по определению (с поправкой на то, что nullptr является валидным значением для указателя). Ибо с невалидными указателями вы не можете работать никак, поскольку любое обращение к невалидному указателю (даже чтение его значения) есть UB.
Т.е., строго говоря, вы даже такое не можете делать оставаясь в рамках корректной программы:
T * ptr = new T();
delete ptr;
// Сейчас ptr невалиден.char raw_ptr_value[sizeof(ptr)];
std::memcpy(raw_ptr_value, &ptr, sizeof(ptr)); // Поздравляю, у вас UB.
Здравствуйте, so5team, Вы писали:
S>Если я понимаю правильно, то когда у нас есть что-то вроде: S>
S>A * ptr = nullptr;
S>
Вовсе не обязательно, чтобы где-то было явное присваивание nullptr. Есть множество мест, где указатель, полученный извне, преобразуется в ссылку. В таких случаях указатель принято проверять, но абсолютной гарантии того, что он всегда ненулевой, никогда нет.
S>Т.е. вы не может сравнить ссылку, полученную из нулевого указателя, ни с чем. Т.к. этой самой ссылки у вас в наличии нет. Есть какой-то мусор, которым оперировать нельзя.
"Странно — жопа есть, а слова нет...".
S>из утверждения "Ссылка тоже может быть нулевой, если создана из нулевого указателя." этого не следовало, имхо.
Это уже пришлось разъяснять дополнительно, в ответ на странные возражения.
S>Я вам больше скажу: любой указатель можно считать валидным по определению (с поправкой на то, что nullptr является валидным значением для указателя). Ибо с невалидными указателями вы не можете работать никак, поскольку любое обращение к невалидному указателю (даже чтение его значения) есть UB.
Если задаться целью подсчета ангелов или чертей на кончике иглы — да. Если иметь целью практическое программирование — нет.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Если иметь целью практическое программирование — нет.
А я правильно понимаю, что практическое программирование это:
"Пишем хоть какой-нибудь код который пусть хоть худо-бедно, но компилируется, чтобы наконец-то приступить к настоящей работе — отладке в debugger'е" ?
Здравствуйте, Евгений Музыченко, Вы писали:
S>>Если я понимаю правильно, то когда у нас есть что-то вроде: S>>
S>>A * ptr = nullptr;
S>>
ЕМ>Вовсе не обязательно, чтобы где-то было явное присваивание nullptr.
Вы думаете, что UB куда-то исчезнет, если такое произойдет неявно?
ЕМ>Есть множество мест, где указатель, полученный извне, преобразуется в ссылку. В таких случаях указатель принято проверять, но абсолютной гарантии того, что он всегда ненулевой, никогда нет.
Отсутствие такой гарантии никак не отменяет того факта, что в языке нет легального способа получить ссылку из нулевого указателя. Следовательно, у вас нет такой штуки, как нулевая ссылка. Легально, по крайней мере.
S>>Т.е. вы не может сравнить ссылку, полученную из нулевого указателя, ни с чем. Т.к. этой самой ссылки у вас в наличии нет. Есть какой-то мусор, которым оперировать нельзя.
ЕМ>"Странно — жопа есть, а слова нет...".
Тут скорее речь о том, что не следует пытаться трогать жопу. Вообще. Не говоря уже о том, чтобы делать это голыми руками.
S>>Я вам больше скажу: любой указатель можно считать валидным по определению (с поправкой на то, что nullptr является валидным значением для указателя). Ибо с невалидными указателями вы не можете работать никак, поскольку любое обращение к невалидному указателю (даже чтение его значения) есть UB.
ЕМ>Если задаться целью подсчета ангелов или чертей на кончике иглы — да. Если иметь целью практическое программирование — нет.
Вообще-то, как говорят, UB в случае обращения к значению невалидного указателя появилось в языке Си (а оттуда и в C++) не просто так. А из-за наличия в прошлом архитектур, на которых указатели обрабатывались и хранились отдельно от других значений. Соответственно, попытка использовать значение указателя после того, как указатель был помечен как невалидный (в результате free, к примеру) проверялась на аппаратном уровне и приводила к порождению аппаратного прерывания.
Никто не знает куда приведет нас будущее и не появятся ли подобные архитектуры вновь.
И никто не знает, как следующие версии компиляторов начнут эксплуатировать существующие UB. А ведь с каждым релизом эти самые компиляторы научаются все больше и больше трактовать UB в свою пользу.
Так что написанный вами сейчас код с UB в рамках "практического программирования" запросто может перестать работать уже буквально завтра. Или послезавтра.
такой код уверенно приводит к Segmentation fault вместо ожидаемого Invalid на -O2. Без оптимизации отрабатывает "корректно". Предлагаете компилировать без оптимизаций?
Здравствуйте, Евгений Музыченко, Вы писали:
К>>К ссылке заранее уровень доверия — "бог".
ЕМ>Только у тех, кто полагает, будто программа исполняется за счет магии.
У тех, кто пишет компилятор.
ЕМ>Ну, или кому просто везет. Кому хоть раз довелось часами-сутками-неделями искать причину случайной порчи памяти, после этого навсегда понимают, что технически ссылка не отличается от любой другой переменной, и чем раньше будет поймано несоответствие, тем меньше времени и нервов придется потратить.
Или кто пишет говнокод, после чего понимает, что технически ссылка ОТЛИЧАЕТСЯ от любой другой переменной. Или всё равно не понимает.
К>>Вот пример программы, которая не валится. ЕМ>Исключительно потому, что работает лишь с адресами, а не указываемыми объектами. К>>И код совсем-совсем одинаковый... почему же он не взаимозаменяемый, а? ЕМ>Может, потому, что в нем изначально была поставлена цель не решить практическую задачу (о чем идет речь в теме), а продемонстрировать (непонятно, зачем) особенности реализации?
Исключительно потому, что это дистиллированный пример.
"Непонятно зачем"? Затем, чтобы дошло, что ссылки не есть указатели, и что логика там — другая.
И это не "особенности реализации", а неопределённое поведение.
Посмотрим на MFC-like говнокод (за точность имён не ручаюсь, давно в MFC не лазил, — передаю общий смысл)
class CWnd {
HWND m_hWnd;
...
// при создании / привязке хэндла к объекту он регистрируется в глобальной табличкеstatic void RegisterHandle(HWND h, CWnd* p);
static void UnregisterHandle(HWND h);
static CWnd* FromHandle(HWND h); // возвращает nullptr если не найдено
...
// MFC любит и умеет работать с нулевым thisbool IsValid() const { return this != nullptr; }
// например, получать хэндл из объекта
HWND GetSafeHWND() const { return IsValid() ? this->m_hWnd : NULL; }
// и дефолтить некоторые вызовы WinAPIbool SetWindowText(LPCTSTR text) { return ::SetWindowText(GetSafeHWND(), text); }
...
};
class CMyWindow : public CWnd { ..... };
class CExtendedMyWindow : public SomeExtension, public CMyWindow { ..... };
.....
CWnd* pWnd = CWnd::FromHandle(h); // мы точно знаем, что h либо INVALID, либо от нашего окна
CMyWindow* pMy = static_cast<CMyWindow*>(pWnd); // 0 превращается в 0
CExtendedMyWindow* pExt = static_cast<CExtendedMyWindow*>(pWnd); // 0 превращается в 0if (pMy->IsValid()) // или if (pMy), или if (pMy->GetSafeHWND()) - это одно и то же
.....
if (pExt->IsValid()) // directed by Robert Weide. 0 превращается в sizeof(SomeExtension)+padding для передачи внутрь CWnd::IsValid
.....
Посмотри. Ты здесь хоть где-нибудь увидел ссылку? Нет? А она есть. Точнее, есть разыменование указателя.
Код полностью корректный для заведомо ненулевых указателей; корректный, если строго следовать конвенции MFC (наследоваться только первой базой), и мгновенно ломается в общем случае.
И это ещё надо сказать спасибо MSVC, который знает, что оптимизировать проверку this!=nullptr = всегда true ему запрещено.