LINUX.ORG.RU

Вопрос новичку на тему C++

 


0

6

Допустим, окончили вы мега-курсы, и приходите устраиваться C++-программистом. Написали «C++» в списке знаний и навыков в резюме, и с гордо поднятой головой шагаете на интервью.

И вот, вам дают такой вопросик. Скажите, что произойдёт при выполнении данного кода (код, разумеется, бредовый, иначе как проверить знание секретов C++?):

#include <iostream>

struct T
{
    int iVal = 0;
    void printValue() const
    {
        std::cout << "Value is " << iVal << std::endl;
    }
    void destruct()
    {
        delete this;
    }
};

int main()
{
    T x{9};
    x.destruct();
    x.iVal = 11;
    x.printValue();
}

Какой правильный ответ, и почему?

★★★★★

Последнее исправление: seiken (всего исправлений: 1)

Так UB же!

delete на стековый объект. У меня core dumped на x86_64. Куда интересней создать в куче. Тут проглатывается и на x86_64, хотя остаётся обращение к объекту в освобождённой памяти. И ещё интереснее сделать вызов функции которая не обращается к внутренним полям, тогда и такое проканает:

T *x = nullptr;
x->foo();

хотя присвоение на x86_64 уже вызовет core dumped.

В общем - UB.

hatred ★★★
()
Ответ на: комментарий от Virtuos86

с подключением сишников в духе «ну и срань же эти кресты»

Как будто в сишечке точно так же нельзя самоубиться об стену:

#include <stdlib.h>
#include <stdio.h>

struct T{
    int iVal;
};

void printValue(struct T* t){
    printf("Value: %d", t->iVal);
}
void destruct(struct T* t) {
    free(t);
}

int main(){
    struct T x = {9};

    printValue(&x);
    destruct(&x);
    x.iVal = 11;
    printValue(&x);

    return 0;
}
WatchCat ★★★★★
()
Последнее исправление: WatchCat (всего исправлений: 1)
Ответ на: комментарий от wandrien

В комментах собрались люди, которые на полном серьёзе рассказывают, как именно будет выполняться код, содержащий UB.

- ты зачем покупаешь газету для дураков, ты что дурак ?
- нет, но очень интересно, что там для них, дураков, пишут 
x905 ★★★★★
()
Ответ на: комментарий от bugfixer

Вот есть у Вас, допустим, 10 MLOC, и они даже как-то работают в Вашей текущей сборке Вашим «любимым» компилятором на Вашем текущем железе. Как Вы планируете там UB искать?

Я планирую уволиться в таком случае. Потому что если изначально этой проблемой никто не озаботился, то проще забить на это говно.

hateyoufeel ★★★★★
()
Ответ на: комментарий от bugfixer

Или, может быть - «static analysis» какой? И сколько человеко-часов на это уйдёт? И при всех этих затратах - откуда возьмётся уверенность что Вы их таки найдёте все?

Ты сейчас rust придумал.

cumvillain
()
Ответ на: комментарий от bugfixer

Давайте сойдемся на том что для конкретной комбинации компилятора / OS / env ответ вполне себе детерминирован?

Только если под env понимать полное состояние памяти в момент запуска программы. Потому что, в общем случае, нарвётся ли delete на защиту ОС — абсолютно случайное событие.

monk ★★★★★
()
Ответ на: комментарий от bugfixer

грань между UB и логическими ошибками в своём коде? С практической точки зрения - я фундаментальной разницы не вижу.

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

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

monk ★★★★★
()
Ответ на: комментарий от bugfixer

словить в проде баг компилятора

Риски всё равно на Вас, и лучше их осознавать / уметь оценивать

«а что будет если XXX»

То есть Вы можете ответить, даже что будет, если выполнить указанную в теме программу на багованном компиляторе? И оценить риски такого события?

monk ★★★★★
()
Ответ на: комментарий от bugfixer

UB - это не логическая ошибка, а такое некорректное состояние исполнителя, которое не определено спецификацией из практических соображений.

В частности, UB может привести к тому, что:

  • Программа не будет скомпилирована, и компилятор выдаст диагностическое сообщение.
  • Программа упадёт при исполнении.
  • Программа исполнит рандомный кусок кода в момент наступления UB.
  • Программа выдаст «невозможный» или абсурдный результат.
  • Любое другое мыслимое и немыслимое событие.

А «практичность соображения» тут состоит в том, что не все виды UB поддаются диагностике в компайл тайме или ран тайме. Если компилятор какие-то случаи мог диагностировать - хорошо. Если не смог - он не виноват.

wandrien ★★
()
Ответ на: комментарий от alex1101

Чатгпт говорит, что полностью избавиться от UB невозможно, но не из-за внутренних противоречий ЯП, а из-за несовершенства оборудования и реализаций компиляторов. Ну это очевидно, вопрос не в этом.

alex1101
()
Ответ на: комментарий от alex1101

Вот тут чувак пишет, что теоретически возможно сделать ЯП без UB: https://habr.com/ru/articles/343882/

И отталкиваться нужно не от Гёделя, а от теоремы Райса:

любое нетривиальное свойство языка, распознаваемого машиной Тьюрига, неразрешимо

alex1101
()
Ответ на: комментарий от alex1101

Не обязательно. Может быть эксплуатация аппаратных особенностей (Spectre, Meltdown, …). Может быть эксплуатация логических ошибок (например, переполнение беззнакового числа в 0 не UB, но проверка может быть пропущена).

monk ★★★★★
()
Ответ на: комментарий от alex1101

Их таких много: Java, Lisp, Python, …

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

monk ★★★★★
()
Ответ на: комментарий от alex1101

Программа для МТ это то, что написано на ее ленте в самом начале, соотв., ЯП для машины тьюринга это множество ее входных символов (подмножество ее алфавита).

soomrack ★★★★★
()
Ответ на: комментарий от monk

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

Хорошо. Смотрите. Вот ошиблись Вы в вычислениях индекса в табличку, и взяли номер квартиры из вчерашней доставки. И сегодняшняя посылочка уехала по pseudo-random адресу. И хорошо если такой квартиры в доме не окажется - Вы это поймаете, но допустим так получилось что в Вашем городе все здания - типовые хрущёвки. Дальше - больше. Хорошо если Вы условную пиццу развозите (who cares about single one, что называется), а если это золотишко или брюлики от Тиффани? Всё становится интересней, не правда ли? Вы конечно мне сейчас скажете что это никакой не UB а прямое нарушение контракта, и вообще - вакханалия. А я парирую тем что у нас на website мелким шрифтом было написано что по пятницам весь офис бухает и, хотя мы и стараемся изо всех сил, доставку на правильный адрес мы по пятницам не гарантируем - отправляйте на свой страх и риск (аналогию с signed integer overflow не улавливаете ещё?)…

Понимаете к чему я? Вы почему-то зациклились на UB на срезе compiler / libstdc++, а их гораздо больше. Если Вы используете любой API - Вы уже заложник.

bugfixer ★★★★★
()
Ответ на: комментарий от wandrien

Вы знаете, одно UB другому UB рознь.

  • Разыменовывать нулевой указатель это UB, но я могу с очень высокой точностью предсказать, как именно будет выполняться такой код.
  • Нарушать strict aliasing это UB, но в ряде случаев результат исполнения практически гарантирован.
  • Обращаться на чтение/запись из нескольких потоков к разделяемой переменной, не используя примитивы синхронизации, это UB, но поведение такого кода тоже более чем предсказуемо.

Не стоит молиться на волшебные два слова в стандарте. Лоб расшибете.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Обращаться на чтение/запись из нескольких потоков к разделяемой переменной, не используя примитивы синхронизации, это UB, но поведение такого кода тоже более чем предсказуемо.

Да неужели. А если это AArch? А если это POWER? А если это одноядерный 16-битный контроллер? А если это отладочный интерпретатор?

wandrien ★★
()
Ответ на: комментарий от bugfixer

Если нет UB, то ошибку всегда можно изолировать. Например, после записи адреса доставки записывать всё в журнал. Или дополнительную проверку добавить, что между заказом и доставкой адрес не изменился.

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

доставку на правильный адрес мы по пятницам не гарантируем - отправляйте на свой страх и риск (аналогию с signed integer overflow не улавливаете ещё?)

Аналогия была бы если бы по пятницам любой заказ на доставку приводил бы к произвольным действиям от имени заказчика. Например, сожжению Корана.

monk ★★★★★
()
Ответ на: комментарий от wandrien

А если это …

Что же вы и про разыменование нулевого указателя, и про strict aliasing не перечислили варианты платформы? Кстати про компилятор и ОС забыли, это тоже очень сильно влияет.

Разумеется поведение будет зависеть от платформы. Обратного я нигде не утверждал. Но это не значит, что оно будет непредсказуемым, и рассуждать о нем бессмысленно.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 2)
Ответ на: комментарий от monk

Бесполезно ловить исключения. UB позволяет что угодно.

Опять эта бредовая мантра. Никого не волнует, что законодательно компилятор имеет право, обнаружив UB, вставлять туда rm -rf / – важно, что а) никто никогда так не делает б) целый класс UB принципиально невозможно обнаружить.

И рассуждать о том, «что если» можно и нужно. Примеры я выше привел.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Да, да, щаззз. Уже забылась история, как в gcc и clang поменялись алгоритмы оптимизации и хакиры, закладывающиеся на «предсказуемое поведение ub, потому что всегда так было» дружно пошли нахрен.

Ничему дураков история не учит. Так и продолжают лабать happen to work код.

wandrien ★★
()
Последнее исправление: wandrien (всего исправлений: 2)
Ответ на: комментарий от wandrien

Не стоит заниматься демагогией. Из того, что в конкретных случаях «неопределенное» поведение оказывается вполне определенным, вовсе не следует, что на него всегда можно и нужно закладываться.

И приведите мне цитату и ссылку на сообщение, в котором я бы кого-то призывал закладываться на UB или писать код, его содержащий.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Никого не волнует, что законодательно компилятор имеет право, обнаружив UB, вставлять туда rm -rf / – важно, что а) никто никогда так не делает б) целый класс UB принципиально невозможно обнаружить.

И рассуждать о том, «что если» можно и нужно.

Забавно. Тут вчера @monk в другой теме привел пример кода на C++ на котором, якобы, компиляторы C++ не смогут применить tail call optimization.

Захотелось проверить так ли это. Попробовал на godbolt. Вроде бы gcc не смог, тогда как msvc смог. Но интереснее всего поступил clang 17, который все выбросил вообще. Я аж офигел.

И только после этого увидел, что в коде monk-а есть UB: функция f не всегда возвращает значение.

Компиляторы выдают предупреждение на это, но я не обратил на это внимания сразу.

Такой вот незамысловатый пример того, что может сотворить компилятор.

eao197 ★★★★★
()

Плюсов не знаю, но своё диванное мнение, скажу честно что подозрение на delete this; типа удалить самого себя изнутри себя эту строчку в гугл и вбил. По ссылкам не переходил и так видно что часто по этому вопросы задают. Вангую что delete отрабатывает в холостую так как всё лежит на стеке и x static struct и просто нечего удалять так как ничего и не выделялось. Если бы оно честно отработало оно бы просто не вернулось в main так как адрес возврата из destruct() был бы похерен и всё бы упало (без понятия как это бы работало). Но это всё больные мысли в слух, гулять так гулять.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Были же примеры, когда UB провоцировало вызов недоступной функции: https://www.reddit.com/r/ProgrammerHumor/comments/10wur63/isnt_c_fun/?rdt=48546

Любое из перечисленного тобой позволяет компилятору сделать то же самое.

Предсказать можно только для конкретной версии компилятора, если знаешь алгоритмы оптимизации.

monk ★★★★★
()
Ответ на: комментарий от eao197

При чем это не только лабораторный пример. При компиляции продакшен-реди кода можно иногда видеть эту диагностику.

И во что она скомпилируется на любой следующей ревизии компилятора, просто бог его знает.

wandrien ★★
()
Ответ на: комментарий от monk

Опять какие-то мантры, никак не связанные с тем, что я написал. Процитирую свой ответ @wandrien

Не стоит заниматься демагогией. Из того, что в конкретных случаях «неопределенное» поведение оказывается вполне определенным, вовсе не следует, что на него всегда можно и нужно закладываться.

А исход в коде выше вполне логичен. Бесконечный цикл – UB, поэтому выпиливается. ret в main не положили. Дальше управление переходит к следующей инструкции, которой оказывается unreachable.

Т.е. это не зловредный компилятор, который тебе палки в колеса вставляет и вызывает непонятно что – просто так сложилось, что в main не оказалось ret, и управление провалилось дальше.

И это в общем-то до определенной степени ожидаемо, магии в этом нет.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Это ожидаемо на нынешнем компиляторе. 10 лет назад ожидаемо не было. А ещё через 10 какая-нибудь оптимизация будет выкидывать всё включая ret после разыменования нулевого указателя. И возможно проваливаться в следующую функцию.

monk ★★★★★
()
Ответ на: комментарий от monk

Возможно будет. Это в целом разумно, хотя есть нюансы.

Но сейчас – нет. Поэтому это ожидаемо, и поэтому рассуждать я об этом могу.

И даже если такое произойдет, в мои рассуждения просто добавится ветвление на новое поколение компиляторов.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Мда… Некоторые бизнесмены по подобным причинам не пишут пункты в договоры. Ведь есть «обычаи делового оборота» и они «всегда так делали»…

В общем, worse is better в полный рост.

monk ★★★★★
()
Ответ на: комментарий от Siborgium

просто надо запретить всяким там компиляторам, устраивать такие вот трюки с оптимизацией. типа ключика -fне-умничай-там. чтобы он циклы не выкидывал.

alysnix ★★★
()