Доступ к указателю на производный класс, когда он не создан

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


#include <iostream>

struct A{
    int a;
     void foo() {
        std::cout << "A = " << a << std::endl;
     }
};

struct B : public A{
    int b;
    void foo() {
        std::cout << "B = " << b << std::endl;
    }
};


int main() {
    A *a = new A();
    B *b = static_cast<B*>(a);

    a->foo(); // cout A = 0
    b->foo(); // cout B = 0
    
    b->b = 333;
    
    b->foo(); // cout B = 333
    a->foo(); // cout A = 0
}

Должен ли указатель на производный класс быть неопределенным?

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

Joseph Sible-Reinstate Monica 18.11.2022 15:11

Что такое «неопределенный указатель»?

Evg 18.11.2022 15:13

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

Eljay 18.11.2022 15:13

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

molbdnilo 18.11.2022 15:13

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

wohlstad 18.11.2022 15:14

Попробуйте скомпилировать с -O3 -Wall -Werror, чтобы получить довольно информативное сообщение об ошибке godbolt.org/z/6WfKsTb8T Тогда учтите, что код имеет точно такую ​​​​же проблему и без -O3.

463035818_is_not_a_number 18.11.2022 15:17

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

drescherjm 18.11.2022 15:17

На самом деле сообщение об ошибке довольно забавное :)

463035818_is_not_a_number 18.11.2022 15:18

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

Peter 18.11.2022 15:21
Как настроить Tailwind CSS с React.js и Next.js?
Как настроить Tailwind CSS с React.js и Next.js?
Tailwind CSS - единственный фреймворк, который, как я убедился, масштабируется в больших командах. Он легко настраивается, адаптируется к любому...
LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Переключение светлых/темных тем
Переключение светлых/темных тем
В Microsoft Training - Guided Project - Build a simple website with web pages, CSS files and JavaScript files, мы объясняем, как CSS можно...
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel могут быть немного сложными, но с помощью Eloquent ORM и его моделей мы можем сделать это с легкостью. В этой...
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Карта дорог Беладжар PHP Laravel
Карта дорог Беладжар PHP Laravel
Laravel - это PHP-фреймворк, разработанный для облегчения разработки веб-приложений. Laravel предоставляет различные функции, упрощающие разработку...
0
9
70
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Static_cast указатель на указатель на производный класс, когда объект, на который указывает, на самом деле не является подобъектом базового класса объекта производного типа, как вы делаете с приведением в B *b = static_cast<B*>(a);, имеет неопределенное поведение.

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

Ответ принят как подходящий

Скомпилируйте с -O3 -Werror -Wall, чтобы получить следующее сообщение от gcc:

<source>: In function 'int main()':
<source>:25:8: error: array subscript 'B[0]' is partly outside array bounds of 'unsigned char [4]' [-Werror=array-bounds]
   25 |     b->b = 333;
      |     ~~~^
<source>:19:18: note: object of size 4 allocated by 'operator new'
   19 |     A *a = new A();
      |                  ^
cc1plus: all warnings being treated as errors

Сообщение несколько забавное, потому что оно пропускает некоторые детали реализации gcc, упоминая unsigned char [4], когда его нет. Однако, если вы подумаете, что не так в вашем коде, сообщение прибивает это....

В памяти объект B выглядит так (очень упрощенно):

  A 
  B

У него есть подобъект A, после которого идут B члены. Теперь получается, что A имеет размер 4 (может быть другим, но это то, что с gcc).

Когда пишешь b->b. Затем b указывает на A (не на ba B), но вы делаете вид, что он указывает на B. Следовательно, когда компилятор пытается применить смещение к указателю для достижения члена, это смещение больше, чем размер объекта. Компилятор понимает, что что-то не может быть правильным.


Однако это сообщение об ошибке не более чем интересное любопытство. Компилятору не требуется диагностировать вашу ошибку, потому что ваш код имеет неопределенное поведение.

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

Почему ваш код компилируется и работает нормально? Это не. b не указывает на B. Ваш код может также вывести "Hello World" на консоль, он может стереть ваш жесткий диск или действительно может произойти что угодно. Только если ваш код не имеет неопределенного поведения, есть гарантия того, что ваша программа будет делать.

Также стоит отметить, что, скорее всего (но не гарантировано), на самом деле происходит то, что b->b = 333 перезаписывает 4 байта памяти после объекта A. Что, пока вы отмечаете важность, кажется, работает - по крайней мере, пока вы не сделаете больше в программе, и эти 4 байта фактически не начнут содержать вещи, которые не должны быть перезаписаны, после чего вы можете начать гоняться за «волшебными» ошибками. из несвязанных переменных, которые, кажется, меняют значение, когда никто не записывает их.

Frodyne 18.11.2022 15:32

@Frodyne да, хотя, по-видимому, компиляция обнаруживает проблему и, таким образом, может даже полностью удалить b->b = 333;. Это просто не оптимизация, от которой действующая программа много выигрывает, поэтому маловероятно, что компилятор сделает это даже с -O3

463035818_is_not_a_number 18.11.2022 16:08

Другие вопросы по теме