Приведение к разным базовым классам дает разные результаты. С++

Может быть, мой вопрос не совсем правильно сформирован, но мой код все прояснит.

#include <iostream>
using namespace std;

struct A{int n;};
struct B{int n;};
struct C : A, B{};

int main()
{
    C c;
    C* pc = &c;

    std::cout<<"TEST1"<<std::endl;
    cout << static_cast<B*>(pc) << "\n";
    cout << reinterpret_cast<B*>(pc)<<"\n\n";

    std::cout<<"TEST2"<<std::endl;
    cout << static_cast<A*>(pc) << "\n";
    cout << reinterpret_cast<A*>(pc)<<"\n";
}

И вывод:

TEST1
0042F830
0042F82C

TEST2
0042F82C
0042F82C

Я знаю, что использование reinterpret_cast — это неправильный дизайн. Я не думаю о дизайне, но поведение меня беспокоит. Может ли кто-нибудь объяснить, почему кастинг разными способами дает разные результаты в первый раз, но один и тот же результат во второй раз??


person Eduard Rostomyan    schedule 23.02.2018    source источник
comment
Использование reinterpret_cast почти всегда свидетельствует о плохом дизайне. Избегайте этого и пересмотрите подход.   -  person Ron    schedule 23.02.2018
comment
static_cast<B*>(pc) - дайте мне расположение B части *pc; reinterpret_cast<B*>(pc) — рассматривать pc как местоположение B.   -  person molbdnilo    schedule 23.02.2018
comment
@FrançoisAndrieux Это настоящий ответ, почему бы вам не опубликовать его как таковой?   -  person mvidelgauz    schedule 23.02.2018
comment
Является ли reinterpret_cast‹› в основном таким же, как приведение в стиле c?   -  person Zebrafish    schedule 23.02.2018
comment
@Zebrafish Нет, приведения в стиле c могут быть любыми. reinterpret_cast изменяет тип указателя без фактического приведения (адрес указателя не изменяется). Вы просто интерпретируете одно и то же воспоминание по-разному.   -  person Bizzarrus    schedule 23.02.2018
comment
@Bizzarrus Итак, если у меня есть int* a; и я знаю, что у меня есть машина с прямым порядком байтов, char* chptr = (char*)a; не обязательно будет указывать на младший значащий байт этого int? Я немного беспокоюсь, потому что я делал это в прошлом, чтобы изменить порядок байтов. Я предполагаю, что приведение в стиле c в C полностью отличается от приведения в стиле c в C++?   -  person Zebrafish    schedule 23.02.2018
comment
@Zebrafish Какой байт целого числа вы получаете (LSB или MSB), зависит от платформы. Вы можете увидеть, как интерпретируется приведение в стиле C в C++.   -  person Olaf Dietsche    schedule 23.02.2018
comment
@Zebrafish В этом случае он будет делать то, что вы хотите, да. Проблема с приведением в стиле c в основном заключается в том, что компилятор выбирает, использовать ли его reinterpret_cast, static_cast или dynamic_cast, поэтому вы должны доверять компилятору, чтобы он знал, что вы хотите. С базовыми типами (int, char и т. д.) он будет использовать reinterpret_cast (насколько я знаю), со сложными типами он может использовать dynamic_cast или static_cast. Поэтому явное указание того, какое приведение вам нужно, может значительно предотвратить ошибки и сделать код более читаемым.   -  person Bizzarrus    schedule 23.02.2018


Ответы (2)


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

struct A {
    int n;
};

и в памяти

| Address  | Size | Member |
|----------+------+--------+
| 0042F82C | 4    | n      |

То же самое происходит с базовыми классами.

struct C : A, B {
};

возможная схема памяти

| Address  | Size | Member |
|----------+------+--------+
| 0042F82C | 4    | A::n   |
| 0042F830 | 4    | B::n   |

Теперь у вас есть указатель pc на объект типа C. Использование static_cast учитывает расположение членов и базовых классов внутри объекта.

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

Использование reinterpret_cast с другой стороны просто повторно использует указатель и делает вид, что он указывает на другой тип.

Пояснение

В отличие от static_cast, но, как и в случае с const_cast, выражение reinterpret_cast не компилируется ни в какие инструкции процессора. Это чисто директива компилятора, которая указывает компилятору обрабатывать последовательность битов (представление объекта) выражения, как если бы оно имело тип new_type.

Это причина, по которой вы получаете одинаковое значение адреса.

person Olaf Dietsche    schedule 23.02.2018

По сути, A и B части C не могут занимать одно и то же место. Одно должно предшествовать другому. Когда вы правильно приводите C* к A*, вы получаете указатель на часть A экземпляра, на которую указывает исходный указатель, и то же самое верно для приведения к B*. Поскольку A часть C (int A::n;) и B часть C (int B::n;) обязательно находятся по разным адресам, естественно, что результаты этих преобразований также отличаются друг от друга. Это возможно, потому что компилятор может знать макет объекта, на который указывает pc, информация выводится из его типа. Это не сработало бы, если бы информация была недоступна, например, если указатель сначала был приведен к void*.

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

Остерегайтесь использования reinterprect_cast, так как он фактически вводит факты в систему безопасности типов. Компилятор обязан предположить, что то, что вы ему говорите, верно. Если эти «факты» не соответствуют действительности (как в случае с reinterpret_cast<B*>(pc)), вы рискуете получить неопределенное поведение.

person François Andrieux    schedule 23.02.2018