LINUX.ORG.RU

Содержит ли данный код UB?

 


0

2
#include <iostream>

class B {
public:
    ~B() {
        std::cout << "Calling B destructor" << std::endl;
    }
};

class A {
public:
    A(int refCount):
        refCount(refCount)
    {}

    virtual ~A() {
        std::cout << "Calling A destructor" << std::endl;
        --refCount;
    }

    static void operator delete (void *p) {
        int refCount = reinterpret_cast<A *>(p)->refCount;

        std::cout << "Attempt to delete A, refCount is " << refCount  << std::endl;

        if (refCount)
            return;

        std::cout << "Actualy deleting A" << std::endl;
        ::operator delete(p);
    }

private:
    int refCount;
    B b;
};

int main(int argc, char* argv[]) {
    const int refCount = 4;

    A * a = new A(refCount);

    for (int i = 0; i < refCount; ++i) {
        delete a;
    }

    return 0;
}

Вывод:

Calling A destructor
Calling B destructor
Attempt to delete A, refCount is 3
Calling A destructor
Calling B destructor
Attempt to delete A, refCount is 2
Calling A destructor
Calling B destructor
Attempt to delete A, refCount is 1
Calling A destructor
Calling B destructor
Attempt to delete A, refCount is 0
Actualy deleting A

Содержит ли данный код undefined behaviour?

# clang++-5.0 -std=c++11 -fsanitize=undefined main.cpp
# ./a.out

говорит что всё хорошо.


Ответ на: комментарий от anonymous

Ну, можно было бы обозвать член класса mRefCount, но сути это не меняет. Любая вменяемая IDE подсветит и будет понятно что к чему.

com
() автор топика

Не уверен - не используй. Опять зацеперы-бейсджамперы от программирования? Откуда вы экстремалы беретесь?

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от anonymous

Вопрос как раз про многократный delete, как вы, наверное, поняли.

com
() автор топика
Ответ на: комментарий от anonymous

В интернете везде пишут что UB, а почему так — не могу найти.

В описании к delete expression написано что указатель должен быть порождён new expression, а вот про двойной вызов ничего нету.

com
() автор топика

Содержит ли данный код undefined behaviour?

Да. Объект уже перестал существовать, но ты дергаешь его метод (деструктор), но никто не гарантирует, что объект действительно физически перестал существовать, так что это UB. Подробнее тут на 257 странице, пункт 16.

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

Содержит ли данный код undefined behaviour?

Да. Вызовы деструкторов приведут объект в «неинициализированное состояние». Повторно что-то делать с таким объектом нельзя (хотя если все поля POD-типы, это даже будет работать, не вызывая возмущения valgrind-а).

-fsanitize

В тех случаях, когда возможно запустить valgrind, лучше запускать valgrind.

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

В тех случаях, когда возможно запустить valgrind, лучше запускать valgrind.

Valgrind тоже ничего не покажёт. Память освобождается только один раз когда доходит до

::operator delete(p);
==15990== HEAP SUMMARY:
==15990==     in use at exit: 72,704 bytes in 1 blocks
==15990==   total heap usage: 3 allocs, 2 frees, 73,744 bytes allocated
==15990== 
==15990== LEAK SUMMARY:
==15990==    definitely lost: 0 bytes in 0 blocks
==15990==    indirectly lost: 0 bytes in 0 blocks
==15990==      possibly lost: 0 bytes in 0 blocks
==15990==    still reachable: 72,704 bytes in 1 blocks
==15990==         suppressed: 0 bytes in 0 blocks
==15990== Reachable blocks (those to which a pointer was found) are not shown.
==15990== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==15990== 
==15990== For counts of detected and suppressed errors, rerun with: -v
==15990== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

fsb4000 ★★★★★
()
$ clang++ -std=c++11 -fsanitize=memory -fsanitize-memory-use-after-dtor -g test.cpp 
$ MSAN_OPTIONS=poison_in_dtor=1 ./a.out 
Calling A destructor
Calling B destructor
Attempt to delete A, refCount is 3
==7372==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0x2c95bd in A::operator delete(void*) /tmp/test.cpp:26:13
    #1 0x2c9a45 in A::~A() /tmp/test.cpp:16:18
    #2 0x2c9106 in main /tmp/test.cpp:44:9
    #3 0x7f200bcb0f29 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x20f29)
    #4 0x24d029 in _start (/tmp/a.out+0x24d029)

SUMMARY: MemorySanitizer: use-of-uninitialized-value /tmp/test.cpp:26:13 in A::operator delete(void*)
Exiting

> -fsanitize
В тех случаях, когда возможно запустить valgrind, лучше запускать valgrind.

Аргументы будут?

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

И правда. Таким образом, я бы мог заново создать объект в operator delete по тому же адресу с помощью placement new, но применение delete expression в дальнейшем к такому объекту тоже приведёт к UB.

Печально.

Хочется аналог std::shared_ptr в куче. Возвращать указатель на std::shared_ptr из метода публичного API совершенно не хочется.

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

Возвращать указатель на std::shared_ptr из метода публичного API совершенно не хочется.

А написать свой smart pointer, не обладая нужными знаниями, хочется.

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

Ну, в реализации предполагал в A хранить reference на B, счётчик положить туда же.

Не хочется плодить объекты в куче для каждого A (их предполагается много).

com
() автор топика
A * a = new A(refCount);

for (int i = 0; i < refCount; ++i) {
  delete a;
}

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

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

Дык, ты возвращай std::shared_ptr<>, а не указатель на него(std::enable_shared_from_this для сложных случаев). А вообще, опиши подробнее, что нужно, а то хрен поймёшь

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

Это в чём упоротость-то?

был у меня знакомый. один раз родился, два раза умер.

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

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

был у меня знакомый. один раз родился, два раза умер.

ДокторКто.жпг

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

Аргументы будут?

Как минимум, не нужно пересобирать все используемые библиотеки. Когда свой говнокод неправильно использует какую-нибудь либу, проце пересобрать ее со включенным debug-ом (или доставить debuginfo-пакет), чем собрать отсанитайженную версию либы (не говоря уже о том, что санитайзить glibc опасно для здоровья).

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

Ни*ера себе знакомые. Аналогия, кстати, не до конца точна - тут скорее sp остался, а знакомый уже rip

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

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

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

Есть библиотека 1, она порождает объект. Есть библиотека 2, она порождает ещё кучу объектов которые пользуются объектом из библиоткеки 1. И есть ещё библиотека 3, которая является JNI обёрткой для библиотеки 1 и 2.

Библиотека 1 порождает объект с помощью фабричной функции. Из этой функции нужно вернуть unique или shared pointer.

Объекты библиотеки 2 хранят shared pointer'ы на объект из библиотеки 1.

Библиотека 3 тоже должна хранить shared на объект из библиотеки 1, но проблема в том, что она должна хранить его в куче (указатель записанный в Java long).

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

Пока самым логичным вариантом выглядит возвращать из библиотеки 1 unique_ptr и пусть пользователи сами создают из него shared.

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

Т. е., проблема из-за Java? В Java не рублю, если честно. Но ничего смертельного пока не видно. Или в это место производительность упирается?

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

Не, не упирается. Для простоты понимания можно заменить Java на С. То есть, необходимо предоставить функциональность shared_ptr для С обёртки так, чтобы от этого не пострадало C++ API. C обёртки пока нету, это задел на будущее.

Логично что С обёртка будет написана в ООП стиле и будет работать с неким HANDLE (указатель на shared_ptr?) на объект библиотеки 1. Выглядит диковато, но работать будет.

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

Если это не является узким местом, оставляй как есть - нормальный интерфейс между языками сделать не особо просто(особено с крестами)

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

Ок, спасибо за совет. Оставлю как есть. Пометил тему как решённую.

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

boost intrusive_ptr сделает то что тебе нужно

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

Не, не упирается. Для простоты понимания можно заменить Java на С. То есть, необходимо предоставить функциональность shared_ptr для С обёртки так, чтобы от этого не пострадало C++ API. C обёртки пока нету, это задел на будущее.

Прочитайте про intrusive smart pointers. Счетчик ссылок на объект тогда будет гарантированно хранится внутри самого объекта. Это позволит, имея просто голый указатель на объект, создать для него smart pointer с корректным инкрементом ссылок. Таким образом указатель на объект можно будет отдать куда-то еще (при условии, что счетчик ссылок у вас не обнулится).

Сделать intrusive smart pointer можно самому, можно использовать std::enable_shared_from_this из C++11, как уже советовали выше, можно взять любую готовую реализацию интрузивных умных указателей на просторах Интернета.

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

Как минимум, не нужно пересобирать все используемые библиотеки.

Пересобирать все используемые библиотеки нужно только для использования MemorySanitizer (который очень немногие используют), и быть может ThreadSanitzer.
Для AddressSanitizer, UndefinedBehaviorSanitizer, IntegerSanitizer этого не нужно.
Конечно, чтобы получать репорты для этих библиотек от UndefinedBehaviorSanitizer, IntegerSanitizer, библиотеки пересобирать нужно.

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

Ну... есть мнение, что в цэпэпэ слишком много случаев UB

Это среди похапешников такое мнение ходит?

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

Они не уточняют, по сравнению с чем «много»? И почему «слишком»? Что, по их мнению бОльшую часть UB можно доопределить, не теряя при этом переносимости и производительности?

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

Да кто ж в детали вдаваться будет - java наше все. Ну и это, rust, конечно же

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

Хочется аналог std::shared_ptr в куче.

Чет мне кажется, что ты сделал по сути make_shared() только неправильный (тот создает примерно такую же структуру пару счетчиков, за которыми следует объект и все в одном блоке).

Так как ты сделал, делать не надо. По сути ты поменял семантику деструктора, он у тебя не уничтожает объект, а делает что-то другое. Если еще сюда добавить тот факт, что такой код будет всех запутывать, то станет совсем очевидно, что такое решение - все только усложняет.

anonymous
()

Лол :-) UB в коде цепепе потому, что большинство кода представляет собой днище :-) Никогда не встречал нормального кода на цепепе, одна параша :-) И настоящие проблемы в жизни нормальных пацанов начинают возникать тогда, когда эту парашу приходится сопровождать :-) Ну а писать нормальный код умеют только только я и ещё царь :-) Лол :-)

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

Тут такое дело - хочу научиться нормальный код писать, но чет никак не осилю. Можно пару советов от гуру?

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

Тут такое дело - хочу научиться нормальный код писать, но чет никак не осилю.

Зачем себя мучить? :-)

Можно пару советов от гуру?

Можно :-) Читай нормальный код, а свой не пиши :-)

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

Но ведь это метод лисперов...

Это не метод лисперов, а совет от гуру для тех, кого природа не одарила талантом писать нормальный код :-) Тут ещё можно посоветовать смириться :-) Лол :-)

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

Спасибо за совет. Но я хотел бы уточнить ещё один случай: что если ни писать код, ни смириться не представляется возможным?

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

Спасибо за совет. Но я хотел бы уточнить ещё один случай: что если ни писать код, ни смириться не представляется возможным?

Пожалуйста :-) Дальше советовать ничего не могу, потому что у меня нет опыта писать парашукод насильно :-) Лол :-)

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.