C
cpp
@cplusplustudy47 подп.
193просмотров
11 февраля 2024 г.
Score: 212
Грабли со static_cast Недавно наткнулся на забавную багу в коде, которая заставила задуматься над поведением, казалось бы банального, static_cast. Давайте рассмотрим следующее дерево наследования: struct Base {}; struct ActualChild : Base {}; struct OtherChild : Base {}; Давайте теперь сделаем static_cast между братскими классами: ActualChild obj; ActualChild actual_ptr = &obj; Base base_ptr = actual_ptr; OtherChild other_ptr = static_cast<OtherChild>(base_ptr); В теории static_cast не должен делать ничего специфичного. Он просто интерпретирует память как объект данного типа. Пока что вроде особых проблем нет, но давайте добавим в класс OtherChild метод и попробуем его вызвать: struct OtherChild : Base { int foo() { return 0; } }; — other_ptr->foo(); Полный код доступен тут: https://godbolt.org/z/Yvr5oMd7h Странным образом результатом такого вызова будет вполне корректный возврат значения 0. Впрочем если у вас опыта с плюсами побольше моего – для вас ничего странного в этом нет. Компилятор просто вставил вызов метода OtherChild::foo() и передал ему соответствующий указатель в качестве свойства this. Это можно и увидеть в окне с ассемблером в следующих строках: mov rdi, qword ptr [rbp - 32] call OtherChild::foo() Тут логично возникает два вопроса: 1. Что будет если метод foo станет виртуальным? 2. Что будет если метод foo решит использовать свойства класса OtherClass. Ответ на первый вопрос я для краткости заметки отложу на следующий раз, а сейчас давайте попробуем использовать какое-нибудь свойство: struct OtherChild : Base { int foo() { return value; } int value; }; … other_ptr->foo(); Полный код: https://godbolt.org/z/raccns5hs Результатом подобного вызова будет считывание случайного куска памяти. Чтобы понять как это работает давайте посмотрим на ассемблер: mov rax, qword ptr [rbp - 8] mov eax, dword ptr [rax] В данном случае в rbp лежит указатель на объект класса ActualChild (this). Компилятор же в данном участке думает что он работает с OtherChild. Для этого класса он знает, что значение value лежит в первых 4 байтах. По сути передав в этот метод объект класса ActualChild мы просто считаем память лежащую в первых 4 байт от этого указателя. Давайте собственно проверим так ли это следующим кодом: struct ValueHolder { int value = 42; }; int main() { ValueHolder holder; OtherChild other_ptr = static_cast<OtherChild>( (void*)&holder); return other_ptr->foo(); } https://godbolt.org/z/b561Yajqa И действительно результатом данной программы будет 42. В следующий раз я попробую поиграться с виртуальными методами и посмотреть как компилятор будет вести себя в этом случае. А в качестве вывода статьи скажу, что если вы не уверены что лежит под указателем на родительский класс – пользуйтесь dynamic_cast, хоть это и дороже и требует RTTI.
193
просмотров
2868
символов
Нет
эмодзи
Нет
медиа

Другие посты @cplusplustudy

Все посты канала →
Грабли со static_cast Недавно наткнулся на забавную багу в к — @cplusplustudy | PostSniper