Re[5]: Сырые указатели в С++1х
От: Кодт Россия  
Дата: 05.04.23 18:30
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>В обсуждаемой ситуации, когда указатель взаимозаменяем со ссылкой, нет никакого смысла его проверять, кроме как в 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.
Мы надеемся, что нам всё равно — непосредственный статик-каст или через разыменование и обратно. Вроде ведь одно и то же написано?
И код совсем-совсем одинаковый... почему же он не взаимозаменяемый, а?
Перекуём баги на фичи!
Re[6]: Сырые указатели в С++1х
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 05.04.23 19:29
Оценка: :))
Здравствуйте, Кодт, Вы писали:

К>К ссылке заранее уровень доверия — "бог".


Только у тех, кто полагает, будто программа исполняется за счет магии. Ну, или кому просто везет. Кому хоть раз довелось часами-сутками-неделями искать причину случайной порчи памяти, после этого навсегда понимают, что технически ссылка не отличается от любой другой переменной, и чем раньше будет поймано несоответствие, тем меньше времени и нервов придется потратить.

К>Вот пример программы, которая не валится.


Исключительно потому, что работает лишь с адресами, а не указываемыми объектами.

К>И код совсем-совсем одинаковый... почему же он не взаимозаменяемый, а?


Может, потому, что в нем изначально была поставлена цель не решить практическую задачу (о чем идет речь в теме), а продемонстрировать (непонятно, зачем) особенности реализации?
Re[2]: Сырые указатели в С++1х
От: cppguard  
Дата: 05.04.23 20:39
Оценка:
Здравствуйте, T4r4sB, Вы писали:

C>>причём, в режиме -O3 инструкции идентичны:


TB>Потому что ты написал не то. В случае со std::string надо передавать std::string_view, а в случае с std::unique_ptr надо передавать указатель на внутренний ресурс при помощи get()


std::string для примера был. А вопрос как раза про то, почему используют get() вместо передачи по ссылке?
Re[3]: Сырые указатели в С++1х
От: T4r4sB Россия  
Дата: 05.04.23 21:08
Оценка: :)
Здравствуйте, cppguard, Вы писали:

C>std::string для примера был. А вопрос как раза про то, почему используют get() вместо передачи по ссылке?


Потому что юник — это уже обёртка над указателем. Если ты передаёшь юник по ссылке, то ты по сути передаёшь ссылку на указатель. Это двойная индирекция, ну говно же.
К тому же когда ты на вход берёшь указатель, ты не прибиваешь пользователя гвоздями к тому, как именно получен этот указатель — как элемент юника, или как указатель на поле какой-то структуры, или указатель на переменную на стеке, больше гибкости.
Re[7]: Сырые указатели в С++1х
От: T4r4sB Россия  
Дата: 05.04.23 21:10
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Может, потому, что в нем изначально была поставлена цель не решить практическую задачу (о чем идет речь в теме), а продемонстрировать (непонятно, зачем) особенности реализации?


Прирешении практических задач подобные мелкие моменты будут возникать регулярно, пусть и в более завуалированном виде.
Re[4]: Сырые указатели в С++1х
От: cppguard  
Дата: 05.04.23 23:04
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Потому что юник — это уже обёртка над указателем. Если ты передаёшь юник по ссылке, то ты по сути передаёшь ссылку на указатель. Это двойная индирекция, ну говно же.

TB>К тому же когда ты на вход берёшь указатель, ты не прибиваешь пользователя гвоздями к тому, как именно получен этот указатель — как элемент юника, или как указатель на поле какой-то структуры, или указатель на переменную на стеке, больше гибкости.

OMG При чём тут вообще ссылка на unique_ptr? Я же код привёл даже. Речь идёт о ссылке на значение, хранимое внутри unique_ptr.
Re[7]: Сырые указатели в С++1х
От: so5team https://stiffstream.com
Дата: 06.04.23 04:20
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>технически ссылка не отличается от любой другой переменной, и чем раньше будет поймано несоответствие


И как вы предлагаете проверять "несоответствия" для ссылок? Технически.
Re[5]: Сырые указатели в С++1х
От: T4r4sB Россия  
Дата: 06.04.23 06:50
Оценка:
Здравствуйте, cppguard, Вы писали:

C>OMG При чём тут вообще ссылка на unique_ptr? Я же код привёл даже.


У тебя в коде ссылка на std::string вместо использования std::string_view, то есть то же двойная индирекция
Re[6]: Сырые указатели в С++1х
От: cppguard  
Дата: 06.04.23 07:18
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>У тебя в коде ссылка на std::string вместо использования std::string_view, то есть то же двойная индирекция




Сырые указатели в С++1х


С++1x, Карл! Это С++11 и С++14 тоже. Кроме того, я уже сказал, что взял string как пример. Пост про operator* vs get(), приём-приём!
Re[8]: Сырые указатели в С++1х
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 06.04.23 07:23
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Прирешении практических задач подобные мелкие моменты будут возникать регулярно, пусть и в более завуалированном виде.


Можно примеров, иллюстрирующих регулярность? Так, чтоб указатели/ссылки использовались исключительно в целях косвенной адресации, но чтоб там возникала объективная потребность в работе именно с самим указателем, а не указываемым объектом.
Re[8]: Сырые указатели в С++1х
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 06.04.23 07:38
Оценка:
Здравствуйте, so5team, Вы писали:

S>И как вы предлагаете проверять "несоответствия" для ссылок? Технически.


Так же, как и для указателей. Например, если адрес всегда должен быть выровнен, достаточно проверить само значение. Если известно, что объект всегда выделяется в куче — вызвать соответствующую функцию, и так далее.
Re[9]: Сырые указатели в С++1х
От: so5team https://stiffstream.com
Дата: 06.04.23 07:46
Оценка: +2
Здравствуйте, Евгений Музыченко, Вы писали:

S>>И как вы предлагаете проверять "несоответствия" для ссылок? Технически.


ЕМ>Так же, как и для указателей.


Валидный указатель можно сравнивать с nullptr. Как вы предлагаете сравнивать с nullptr ссылку?

ЕМ>Например, если адрес всегда должен быть выровнен, достаточно проверить само значение.


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

Могли бы вы рассказать, какое отношение проверка правильности выравнивания объекта в памяти имеет к исходному тезису "Ссылка тоже может быть нулевой, если создана из нулевого указателя."?
Re[10]: Сырые указатели в С++1х
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 06.04.23 08:16
Оценка: :)
Здравствуйте, so5team, Вы писали:

S>Как вы предлагаете сравнивать с nullptr ссылку?


(&ref != nullptr)

S>Если мне не изменяет склероз, то разговор начался с того, что вы стали защищать тезис о том, что ссылку можно получить и для нулевого указателя.


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

S>И не захотели согласиться с тем, что такие случаи рассматриваться не должны, т.к. это явная ошибка.


Если это делается умышленно — да, ошибка явная. Но это может произойти и неумышленно.

S>Могли бы вы рассказать, какое отношение проверка правильности выравнивания объекта в памяти имеет к исходному тезису "Ссылка тоже может быть нулевой, если создана из нулевого указателя."?


Самое непосредственное. Если ссылка создается путем преобразования указателя, всегда есть вероятность того, что он может оказаться и нулевым, и невалидным. Даже если ссылка создается прямиком из статического объекта, она не перестает быть неявным указателем, который может быть испорчен при банальном переполнении буфера, не говоря уже о малозаметных ошибках в адресной арифметике. Если об этом никогда не думать, считая любую ссылку валидной по определению, можно потратить много времени на поиски ошибок вовсе не там, где их следует искать.
Re[11]: Сырые указатели в С++1х
От: so5team https://stiffstream.com
Дата: 06.04.23 08:42
Оценка: +4 -1
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, 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.
Re[12]: Сырые указатели в С++1х
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 06.04.23 09:33
Оценка: -3 :)
Здравствуйте, so5team, Вы писали:

S>Если я понимаю правильно, то когда у нас есть что-то вроде:

S>
S>A * ptr = nullptr;
S>


Вовсе не обязательно, чтобы где-то было явное присваивание nullptr. Есть множество мест, где указатель, полученный извне, преобразуется в ссылку. В таких случаях указатель принято проверять, но абсолютной гарантии того, что он всегда ненулевой, никогда нет.

S>Т.е. вы не может сравнить ссылку, полученную из нулевого указателя, ни с чем. Т.к. этой самой ссылки у вас в наличии нет. Есть какой-то мусор, которым оперировать нельзя.


"Странно — жопа есть, а слова нет...".

S>из утверждения "Ссылка тоже может быть нулевой, если создана из нулевого указателя." этого не следовало, имхо.


Это уже пришлось разъяснять дополнительно, в ответ на странные возражения.

S>Я вам больше скажу: любой указатель можно считать валидным по определению (с поправкой на то, что nullptr является валидным значением для указателя). Ибо с невалидными указателями вы не можете работать никак, поскольку любое обращение к невалидному указателю (даже чтение его значения) есть UB.


Если задаться целью подсчета ангелов или чертей на кончике иглы — да. Если иметь целью практическое программирование — нет.
Re[13]: Сырые указатели в С++1х
От: Voivoid Россия  
Дата: 06.04.23 09:59
Оценка: +2
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Если иметь целью практическое программирование — нет.

А я правильно понимаю, что практическое программирование это:
"Пишем хоть какой-нибудь код который пусть хоть худо-бедно, но компилируется, чтобы наконец-то приступить к настоящей работе — отладке в debugger'е" ?
Re[13]: Сырые указатели в С++1х
От: so5team https://stiffstream.com
Дата: 06.04.23 10:07
Оценка: +3
Здравствуйте, Евгений Музыченко, Вы писали:

S>>Если я понимаю правильно, то когда у нас есть что-то вроде:

S>>
S>>A * ptr = nullptr;
S>>


ЕМ>Вовсе не обязательно, чтобы где-то было явное присваивание nullptr.


Вы думаете, что UB куда-то исчезнет, если такое произойдет неявно?

ЕМ>Есть множество мест, где указатель, полученный извне, преобразуется в ссылку. В таких случаях указатель принято проверять, но абсолютной гарантии того, что он всегда ненулевой, никогда нет.


Отсутствие такой гарантии никак не отменяет того факта, что в языке нет легального способа получить ссылку из нулевого указателя. Следовательно, у вас нет такой штуки, как нулевая ссылка. Легально, по крайней мере.

S>>Т.е. вы не может сравнить ссылку, полученную из нулевого указателя, ни с чем. Т.к. этой самой ссылки у вас в наличии нет. Есть какой-то мусор, которым оперировать нельзя.


ЕМ>"Странно — жопа есть, а слова нет...".


Тут скорее речь о том, что не следует пытаться трогать жопу. Вообще. Не говоря уже о том, чтобы делать это голыми руками.

S>>Я вам больше скажу: любой указатель можно считать валидным по определению (с поправкой на то, что nullptr является валидным значением для указателя). Ибо с невалидными указателями вы не можете работать никак, поскольку любое обращение к невалидному указателю (даже чтение его значения) есть UB.


ЕМ>Если задаться целью подсчета ангелов или чертей на кончике иглы — да. Если иметь целью практическое программирование — нет.


Вообще-то, как говорят, UB в случае обращения к значению невалидного указателя появилось в языке Си (а оттуда и в C++) не просто так. А из-за наличия в прошлом архитектур, на которых указатели обрабатывались и хранились отдельно от других значений. Соответственно, попытка использовать значение указателя после того, как указатель был помечен как невалидный (в результате free, к примеру) проверялась на аппаратном уровне и приводила к порождению аппаратного прерывания.

Никто не знает куда приведет нас будущее и не появятся ли подобные архитектуры вновь.
И никто не знает, как следующие версии компиляторов начнут эксплуатировать существующие UB. А ведь с каждым релизом эти самые компиляторы научаются все больше и больше трактовать UB в свою пользу.

Так что написанный вами сейчас код с UB в рамках "практического программирования" запросто может перестать работать уже буквально завтра. Или послезавтра.
Re[11]: Сырые указатели в С++1х
От: sergii.p  
Дата: 06.04.23 11:29
Оценка: 120 (1)
Здравствуйте, Евгений Музыченко, Вы писали:

S>>Как вы предлагаете сравнивать с nullptr ссылку?


ЕМ>(&ref != nullptr)


#include <iostream>

std::string* str(int i) {
    return i == 0 ? nullptr : new std::string{std::to_string(i)};
}

std::string foo(const std::string& str) {
    return &str == nullptr ? "Invalid" : str;
}

int main() {
    std::string * pstr = str(0);
    std::cout << foo(*pstr);
}


такой код уверенно приводит к Segmentation fault вместо ожидаемого Invalid на -O2. Без оптимизации отрабатывает "корректно". Предлагаете компилировать без оптимизаций?
Re[7]: Сырые указатели в С++1х
От: Кодт Россия  
Дата: 06.04.23 11:38
Оценка: +1
Здравствуйте, Евгений Музыченко, Вы писали:

К>>К ссылке заранее уровень доверия — "бог".


ЕМ>Только у тех, кто полагает, будто программа исполняется за счет магии.


У тех, кто пишет компилятор.

ЕМ>Ну, или кому просто везет. Кому хоть раз довелось часами-сутками-неделями искать причину случайной порчи памяти, после этого навсегда понимают, что технически ссылка не отличается от любой другой переменной, и чем раньше будет поймано несоответствие, тем меньше времени и нервов придется потратить.


Или кто пишет говнокод, после чего понимает, что технически ссылка ОТЛИЧАЕТСЯ от любой другой переменной. Или всё равно не понимает.


К>>Вот пример программы, которая не валится.

ЕМ>Исключительно потому, что работает лишь с адресами, а не указываемыми объектами.
К>>И код совсем-совсем одинаковый... почему же он не взаимозаменяемый, а?
ЕМ>Может, потому, что в нем изначально была поставлена цель не решить практическую задачу (о чем идет речь в теме), а продемонстрировать (непонятно, зачем) особенности реализации?

Исключительно потому, что это дистиллированный пример.
"Непонятно зачем"? Затем, чтобы дошло, что ссылки не есть указатели, и что логика там — другая.

И это не "особенности реализации", а неопределённое поведение.

Посмотрим на 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 любит и умеет работать с нулевым this
  bool IsValid() const { return this != nullptr; }
  // например, получать хэндл из объекта
  HWND GetSafeHWND() const { return IsValid() ? this->m_hWnd : NULL; }
  // и дефолтить некоторые вызовы WinAPI
  bool 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 превращается в 0

if (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 ему запрещено.
Перекуём баги на фичи!
Re[11]: Сырые указатели в С++1х
От: rg45 СССР  
Дата: 06.04.23 11:40
Оценка: +3 :)))
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>(&ref != nullptr)


Гениально!

--
Не можешь достичь желаемого — пожелай достигнутого.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.