LINUX.ORG.RU

Ссылка на содержимое пересозданного указателя

 , ,


1

1

Привет, ЛОР. Попался мне такой извращённый код:

В объявлении класса:

QSettings* realSettings;
QSettings& userSettings;

В заголовке конструктора:

realSettings(new QSettings()), userSettings(*realSettings),

И в одном из методов:

delete realSettings;
realSettings = new QSettings(...);

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

ВНИМАНИЕ, ВОПРОС: что вообще при этом должно произойти со ссылкой? Она должна сохранять валидность? Или это вообще UB? Как это ни смешно, в тестах оно работает и не падает.

Пока очень хочется завернуть код, чтобы всё это извращение переписали на работу с указателями, но код будет более многословный, да. Автор горячится и доказывает, что ссылки придумали специально для того, чтобы они никогда не были невалидными. Я же как-то привык, что ссылка — это «относительно безопасный указатель», и здесь вот эта относительность проявляется. (Что нам вообще мешало вызвать delete, но не вызывать new?)

Если UB — то лучше переписать, конечно.

★★★★★

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

Хм, забавный вопрос.
Ссылка, насколько помню, это просто альтернативное имя. Т.е. связь происходит по имени, а не по адресу. Мне кажется, что код должен работать без проблем.

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

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

Deleted
()

писать и читать конфиги разного типа, но с одинаковым содержимым

А может быть, конфиги одинакового типа, но с разным содержимым? Это было бы более логично.


В заголовке конструктора:
realSettings(new QSettings()), userSettings(*realSettings),
И в одном из методов:
delete realSettings;

Если userSettings - это ссылка, то по-хорошему она должна инициализироваться в момент создания и больше не меняться. Я плохо знаю плюсы, у меня только предположение:

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

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

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

Я вот тоже так понимаю, что по внутреннему представлению ссылка — это указатель, только снабжённый мегахитрожопым синтаксисом, сводящим к минимуму возможность выстрелить в ногу (но не отменяющим эту возможность полностью, как видно из моего примера). Помню, например, что sizeof указателя возвращает именно размер адреса, а sizeof ссылки — размер объекта по ссылке...

Одно непонятно — почему тесты работают?

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

Но сам указатель никуда не девается, и лежит все время в одном и том же месте.

Если ты про realSettings, то это далеко не так. Совершенно не факт, что new создаст объект по тому же адресу, что и уничтоженный по delete.

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

Я тоже. Как я понял, насущная необходимость была в том, что в классе было чуть не полсотни записей типа

userSettings.setValue(...)

А потом появились конфиги разных форматов, один из которых, кстати, вообще пользовательский и создан через QSettings::registerFormat(), и автору очень не хотелось переводить всё это на запись вида

userSettings->setValue(...)

И он прибёг к тёмной магии ссылок...

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

UB. Это ссылка не на указатель, а на данные по этому указателю, она становится не валидна после delete. new может выделить память в другом месте.

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

new может выделить память в другом месте.

Куда смешнее, что в тестах он её, похоже, регулярно выделяет в одном и том же.

Ладно, я похоже, услышал именно то, что хотел, спасибо. Отмечать тему решённой пока подожду, вдруг ещё кто что-то умное напишет...

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

Одно непонятно — почему тесты работают?

UB конечно.

#include <iostream>

int main()
{
    int* real = new int(5);
    int& user = *real;
    std::cout << user << std::endl;
    delete real;
    real = new int(8);
    std::cout << user << std::endl;
    delete real;
}
g++ main.cpp -O3 -fsanitize=address -fsanitize=undefined -fsanitize=leak -fno-omit-frame-pointer
=1057==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x562f2f8ea4a0 bp 0x7fffca7b92e0 sp 0x7fffca7b92d0
READ of size 4 at 0x602000000010 thread T0
    #0 0x562f2f8ea49f in main (/home/fsb4000/test/1/a.out+0x249f)
    #1 0x7f4b5e8b3ce2 in __libc_start_main (/usr/lib/libc.so.6+0x23ce2)
    #2 0x562f2f8ea69d in _start (/home/fsb4000/test/1/a.out+0x269d)

0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
    #0 0x7f4b5f96d791 in operator delete(void*, unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cc:151
    #1 0x562f2f8ea2fd in main (/home/fsb4000/test/1/a.out+0x22fd)
    #2 0x7f4b5e8b3ce2 in __libc_start_main (/usr/lib/libc.so.6+0x23ce2)

previously allocated by thread T0 here:
    #0 0x7f4b5f96c099 in operator new(unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cc:90
    #1 0x562f2f8ea178 in main (/home/fsb4000/test/1/a.out+0x2178)
    #2 0x7f4b5e8b3ce2 in __libc_start_main (/usr/lib/libc.so.6+0x23ce2)

SUMMARY: AddressSanitizer: heap-use-after-free (/home/fsb4000/test/1/a.out+0x249f) in main

Может RazrFalcon и прав на счёт раста, никто не хочет использовать встроенные в компилятор средства проверки...

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

Вот почему кстати нужно делать тесты с санитайзерами.

«Тесты» у меня тоже проходят :)

clang++ main.cpp
./a.out 
5
8
clang++ -v
clang version 8.0.0 (tags/RELEASE_800/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-pc-linux-gnu/8.3.0
Found candidate GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.3.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0
Found candidate GCC installation: /usr/lib64/gcc/x86_64-pc-linux-gnu/8.3.0
Selected GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.3.0
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Selected multilib: .;@m64
g++ main.cpp
./a.out 
5
8
g++ -v
Используются внутренние спецификации.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/8.3.0/lto-wrapper
Целевая архитектура: x86_64-pc-linux-gnu
Параметры конфигурации: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release --enable-default-pie --enable-default-ssp --enable-cet=auto
Модель многопоточности: posix
gcc версия 8.3.0 (GCC) 
fsb4000 ★★★★★
()
Ответ на: комментарий от hobbit

А что там RazrFalcon писал по этому вопросу?

Да писал, что Раст безопасен а С++ лажа, когда я спросил, какую утечку/гонки находит раст и не находят санитайзеры, он начал говорить, что так как программу нужно специально компилировать и запускать, то санитайзеры чушь, а раст гарантирует безопастность на этапе компиляции.

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

Спасибо, возьму на вооружение.

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

-fsanitize=thread -fno-omit-frame-pointer

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

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

с gcc 5 наверное, может и раньше.

clang примерно c 3.1

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

А вообще ещё санитайзеры gcc и clang слегка отличаются по возможностям, даже есть страничка сравнений, clang там вроде покруче:

https://github.com/google/sanitizers/wiki/AddressSanitizerClangVsGCC-(6.0-vs-...

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

fsb4000 ★★★★★
()

UB.

Создал ссылку из разыменованного указателя по адресу А. Удалил. Присвоил указателю новый адрес Б. Что по адресу А - никому не известно.

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

Раст безопасен а С++ лажа

Эта тема тому пример.

Для вас гарантии этапа компиляции и надежда на то, что забагованный код запустится во время тестов под санитайзером - одно и то же?

RazrFalcon ★★★★★
()

Пока очень хочется завернуть код, чтобы всё это извращение переписали на работу с указателями

Указатель на указатель спасёт твоего бойца. Но.

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

Вот тут не понял. Каким образом ссылка здесь поможет? Расскажи ему про принцип KISS. В мире c++ это очень актуально.

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

Ну то есть на Расте норм такое говно писать? А че, компилятор же позаботится!

Говнокод, к сожалению, от этого не уйдет. Скорее с таким подходом его больше станет, а соломка не везде постелена...

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

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

Конечно, когда программа не скомпилируется это лучше чем самому проверять упадёт или нет на санитазейрах.

Но просто мне достаточно того, что дают санитайзеры и clang-tidy/pvs-studio, и пока не возникло желание учить новый язык...

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

Но сам указатель никуда не девается, и лежит все время в одном и том же месте.

Если ты про realSettings, то это далеко не так. Совершенно не факт, что new создаст объект по тому же адресу, что и уничтоженный по delete.

Подожди.

1. Есть указатель realSettings и есть объект. Объект создается в куче.

2. Под переменную указателя realSettings (например 8 байт) память выделяется на стеке. В эту память засовывается адрес объекта в куче.

3. Еще создается ссылка userSettings, которая инитится указателем realSettings. По сути, под ссылку тоже выделяется 8 байт на стеке, и там хранится адрес указателя realSettings.

4. Если удаляется и создается объект, то меняется адрес, записанный в 8 байтах указателя realSettings. На ссылку userSettings это не влияет, она все так же указывает на указатель realSettings.

Я понимаю, что я где-то ошибаюсь и не догоняю. Но на каком моменте?

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

3. Еще создается ссылка userSettings, которая инитится указателем realSettings. По сути, под ссылку тоже выделяется 8 байт на стеке, и там хранится адрес указателя realSettings.

Нет. Указатель на realSettings разыменовывается. Там звёздочка стоит userSettings(*realSettings).

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

Я понимаю, что я где-то ошибаюсь и не догоняю. Но на каком моменте?

На шаге 3:

Еще создается ссылка userSettings, которая инитится указателем realSettings.

На самом деле:

userSettings(*realSettings)
То есть инитится от *указатель, а *указатель это объект. То есть в ссылке сохраняется адрес объекта, так как указатель вообще не передаётся

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

пока не возникло желание учить новый язык...

А у меня не возникает желания пердолиться с санитайзерами. Пусть компилятор за меня думает.

RazrFalcon ★★★★★
()

UB

ибо ссылка указывает на объект,которым манипулирует realSettings, в идеале если объект создается 1 раз за весь цикл программы работать будет,но если realSettings создается и удаляется несколько раз,то одному богу известно когда у тебя вылетит сегфолт.

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

А блин. А я еще подумал: вона как хитро можно сделать, ссылка вроде как не может существовать без объекта, но если объект скормить через указатель «по цепочке», то видимо можна и так.

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

На двух пересозданиях в тесте не вылетел.

Но в итоге постановили сделать всё на указателях.

Помечаю тему решённой.

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

это косяк программиста. причём жосский. это не UB, а ошибка в генах. и в культе полно таких косяков, мелких и крупных.

можно было бы использовать in place new. но это костыль. а костылей там и так навалом. лучше вообще это не использовать.

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

Но в итоге постановили сделать всё на указателях.

А почему не на умных указателях?

Xintrea ★★★★★
()

Ссылка ничем под капотом не отличается от указателя. Просто ей в отличии от указателя нельзя переприсвоить значение, плюс синтаксис обращения без необходимости разыменовывать. В общем, обычный синтаксический сахар. А на деле userSettings является указателем на объект созданный в конструкторе. Очевидно, после delete realSettings он станет невалидным. Программа может не падать, потому что хоть память и помечена свободной, она может какое то время сохранять старые значения (пока не будет перезаписана чем-то новым), плюс munmap делается блоками кратными 4 КБ, так что delete в общем случае не гарантирует мгновенную невалидность адреса. Можешь попробовать запустить под valgrind, скорее всего ударит по рукам за обращения к userSettings после delete.

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

Куда смешнее, что в тестах он её, похоже, регулярно выделяет в одном и том же.

Не обязательно. Просто везёт и память по старому адресу не unmap-пится и не перезаписывается новыми данными, а для кода после delete окей, что теперь у нас фактически userSettings и realSettings несвязанные объекты.

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

и в культе полно таких косяков, мелких и крупных.

«Культя»-то здесь при чём? Вместо QSettings мог быть любой другой класс. Вопрос был именно в том, как сочетается применение ссылок и традиционных указателей. Выяснили, что ссылки в ряде случаев могут быть невалидными.

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

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

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

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

anonymous
()

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

#include <iostream>

Лол. Передавайте привет автору и покажите ему вот этот код.

void foo(int& bar)
{
  std::cout << bar << "\n";
}

int main()
{
  int* p = nullptr;
  foo(*p);
}
anonymous
()
Ответ на: комментарий от KivApple

В общем, обычный синтаксический сахар.

Не синтаксический, а семантический. Семантика значений, слышал, да?

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

Самое главное, что упадёт не на «разыменовывании» в main, а на доступе по ссылке в foo (хотя, конечно, формально UB происходит в main). Ибо под капотом ссылки и указатели ничем не отличаются.

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

Самое главное, что упадёт не на «разыменовывании» в main, а на доступе по ссылке в foo.

Не важно где упадёт. Самое главное, что утверждение «ссылки придумали для того, чтобы они всегда были валидными» - это ложь. Ссыла может быть висячей. Ссылки придумали для того, чтобы можно было писать f(x) вместо f(&x), и x + y вместо &x + &y.

Ибо под капотом ссылки и указатели ничем не отличаются.

Другими слоами, ссылки, как и указатели, хранят адреса объектов, именами которых они и являются. Разница в семантике.

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

я не читала весь тред. прочитала вопрос и ответила.

в коде культи я видела много ошибок. я её не люблю.

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

я не коллекционирую подобный шлак. я когда увидела эти косяки, я закрыла код и больше не возвращалась к коду культи.

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.