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)
Ответ на: комментарий от seiken

Если я правильно понимаю, то здесь происходит хрень с разыменованием указателя на стеке, а дальше идёт UB. Но так как функция PrintValue константная, она никуда не исчезает, потому и выводит.

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

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

Ну так а с gcc 9.4.0 оно либо сразу сегфолтится, либо абортится после «munmap_chunk(): invalid pointer», либо абортится после «free(): invalid pointer», рандомно. Так что практический запуск мало что говорит о самом коде.

seiken ★★★★★
() автор топика

Переменная x на стеке, ибо делается не по new, и делитится, как будто она на куче. Куча сломается. но поскольку переменная еще на стеке, то в нее все еще можно писать значения(впрочем и на куче можно было б писать после delete).

то есть - сломал кучу, но кой чего с переменной пока можно сделать. когда полезешь в кучу c new, все быстро рухнет.

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

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

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

В плюсах структуры и классы это одно и то же по сути, там разница только в public/private по умолчанию.

Меня смущает, что delete используется для указателя, что это значит вообще?

alex1101
()

если ты в классе написал функцию с delete, то ты:

  • или должен гарантировать что обьект можно создавать только по new, а не на стеке или в глобальной памяти.

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

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

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

как это проверить? ну это может проверить delete, попадает ли адрес в диапазон кучи, но не знаю гарантировано ли это стандартом. но если не попадает - то это эксепшн должен быть. поскольку прога явно кривая

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

Ты завалил интервью

Печалька :(

Конечно, что переменная выделена на стеке, а удалена как с кучи - это понятно, что стек переколбасит.

Но вот про this - это трэш. Я привык со структурами - как со структурами, с классами - как с классами. Не труЪ-крестовик я, короче.

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

this будет адресом начала обьекта на стеке, поскольку он обьявлен в функции как локальная переменная, по значению,а не как указатель в кучу, где его создали по new.

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

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

правильных ответов - два

или создавайте обьект на стеке(как сейчас) и не вызывайте ему delete

или создавайте по new, и обращайтесь к нему через ->, поскольку это будет указатель.

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

как-то же стандартная либа определяет, что «free(): invalid pointer»

не смотрел реализацию этих либ. я свои либы писал :)

  • проверять можно по попаданию в диапазон кучи
  • правильно ли выровнен адрес, поскольку куча выравнивает адрес
  • сильно умные кучи(для защиты) могут писать в пролог выдаваемого блока пару магических байтов и смотреть при деаллокации - есть ли эти байты в наличии - если нет, но адрес выровнен и попадает в диапазон - то кучу чем-то расписали в данном месте или данный блок вообще не выдавался, или был уже деаллокирован.
alysnix ★★★
()
Ответ на: комментарий от alysnix

или создавайте по new, и обращайтесь к нему через ->, поскольку это будет указатель.

Как мило. Указатели на что-то созданное на стеке передавать в принципе нельзя? Разыменовывать указатели и таскать by-ref тоже нельзя?

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

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

Как мило. Указатели на что-то созданное на стеке передавать в принципе нельзя? Разыменовывать указатели и таскать by-ref тоже нельзя?

где я это сказал??? я сказал, что нельзя вызывать delete, на область памяти которая не была выделена кучей. это все что я сказал.

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

где я это сказал???

Конкретно вот это:

или создавайте по new, и обращайтесь к нему через ->, поскольку это будет указатель

прозвучало очень двусмысленно.

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

Хорошо, хорошо :)

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

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

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

в классичекой реализации менеджера кучи(без сборки мусора) нет «таблицы выделенных кусков», поскольку она не нужна. есть список(списки) свободных блоков.

но дурное дело нехитрое, можно и спиcок выделенных блоков нагородить, и замедлить себе и new и delete, поскольку этот список надо всегда держать в согласованном состоянии. а зачем он нужен - совсем непонятно.

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

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

Но! Я попробовал создать этот Х в куче. Как ни странно, у меня работает и после delete.

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

По идее, кстати, delete может стек сломать, а не кучу. Ему ж передали указатель на стек. Он отступит от него свои байты и… дальше будет искать свою служебную информацию. Будет ли он записывать что-то по этому отступу – хз. Если будет, то сломает стек. Ну в зависимости от того, что там лежит на стеке.

hibou ★★★★★
()

UB.

Случается delete объекта на стеке. А delete можно только объект в куче. Аллокатор может сделать abort на каком-нибудь assert, может зависнуть, может запороть стек и не суметь вернуться из функции, а может ничего не случится.

Если повезло и аллокатор ничего не сделал, то скорее всего выведет 11. Но опять же нет 100% гарантий, ибо оптимизатор не дремлет и может сделать что-то странное.

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

У меня, кстати, G++ выводит предупреждение на О1, что делается делит неаллоцированного объекта. Если О1 не указывать, то предупреждения нет. Значит при остальных О, наверняка оптимизатор что-то вытворяет.

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

Константность функции не имеет значения. Вот то что она не виртуальная, это может влиять, ибо delete может перезаписать указатель на таблицу виртуальных методов в начале объекта.

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

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

Задать встречный вопрос интервьюеру: это мне у вас в таком говне копаться придется?

Это проверка на честность? Ведь правильный ответ всегда «Да». Или кто-то видел что-то другое?

Kroz ★★★★★
()

Ну так а с gcc 9.4.0 оно либо сразу сегфолтится

Я вообще ничего на с++ не писал кроме одно работы в универе 20 лет назад, я жабокодер, но чисто логикой я не вижу причин для segfalt а поведение clang вполне понятно.
Возможно gcc сегфолт потому что там оптимизация не выкручена на максимум и происходит зануление.

T x{9};

Это же аллокация не в куче а в стеке. Дальше destruct, но по факту в стеке все остается по старому. Вот если бы после x.destruct() ты бы объявил другую структуру, или сделал бы вызов какого-нибудь метода то она бы попортила стек и тогда точно все пошло бы не так.

Разве не?

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

delete для объекта на стеке не освобождает память на стеке от слова совсем. Он может её испортить, он может крашнуться или зависнуть. Память на стеке вообще ничто не способно освободить, кроме }, на то это и стек. Можно вспомнить, что у функции alloca нет парной функции типа freea.

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

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

при delete, в данное место памяти будут прописаны - указатель на следующий свободный блок, размер блока, и «магическое слово»(но это зависит от философии менеджера кучи).

итак кусок стека вам занесли в список свободных блоков менеджера кучи, расписав стек за данным обектом служебной инфой. поскольку у вас размер обьекта в размер int, а писать менеджер кучи будет 2-3 инта туда, то вам распишут область за обьектом. проверьте это сами, сделав там перемиенную.

потом туда записали 11, и поломали этот список свободных блоков совсем. теперь чтобы проверить, что куча сломана, надо просто попросить у кучи 1 байт, через malloc какой-нить. впрочем менеджер может вам и отдать этот блок, если просто берет из списка свободных блоков - первый подходящий. вообще менеджеры устроены не совсем уж так просто, обчно они хранят свободные блоки разных размеров в разных списках, чтобы искать быстрее первый подходящий, например.

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