Мы можем посмотреть на представление объекта типа T
путем преобразования T*
который указывает на этот объект в char*
, По крайней мере, на практике:
int x = 511;
unsigned char* cp = (unsigned char*)&x;
std::cout << std::hex << std::setfill('0');
for (int i = 0; i < sizeof(int); i++) {
std::cout << std::setw(2) << (int)cp[i] << ' ';
}
Это выводит представление 511
в моей системе: ff 01 00 00
,
Здесь (конечно) определенное поведение, определяемое реализацией, происходит здесь. Какой из приведений позволяет мне преобразовать int*
для unsigned char*
и какие преобразования это влечет за собой? Я вызываю неопределенное поведение, как только произнесу? Могу ли я бросить любой T*
типа как это? На что я могу положиться при этом?
Какой из приведений позволяет мне преобразовать
int*
дляunsigned char*
?
Это бросок в стиле C в этом случае такой же, как reinterpret_cast<unsigned char*>
,
Могу ли я разыграть любой тип T * как этот
И да и нет. Часть да: вы можете безопасно привести любой тип указателя к char*
или же unsigned char*
(с соответствующими const
и / или volatile
классификаторы). Результат определяется реализацией, но это законно.
Часть no: стандарт явно разрешает char*
а также unsigned char*
как тип цели. Однако вы не можете (например) безопасно разыграть double*
для int*
, Сделайте это, и вы пересекли границу от поведения, определенного для реализации, до поведения, не определенного. Это нарушает строгое правило наложения имен.
Ваши актеры отображаются на:
unsigned char* cp = reinterpret_cast<unsigned char*>(&x);
Основное представление int
определяется реализацией, и просмотр ее в виде символов позволяет вам это проверить. В вашем случае это 32-битный байтовый порядок байтов.
Здесь нет ничего особенного — этот метод проверки внутреннего представления действителен для любого типа данных.
C ++ 03 5.2.10.7: Указатель на объект может быть явно преобразован в указатель на объект другого типа. За исключением того, что преобразование r-значения типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 являются типами объектов и где требования к выравниванию T2 не являются более строгими, чем требования к T1) и обратно к его исходному типу, дает исходное значение указателя, результат такого преобразования указателя не определен.
Это говорит о том, что приведение приводит к неуточненное поведение. Но, прагматически говоря, приведение от любого типа указателя к char*
всегда позволит вам изучить (и изменить) внутреннее представление ссылочного объекта.
Приведение в стиле C в этом случае эквивалентно reinterpret_cast. Стандарт описывает семантику в 5.2.10. В частности, в пункте 7:
«Указатель на объект может быть явно преобразован в указатель на
другой тип объекта. 70 Когда значение v типа «указатель на T1» равно
преобразуется в тип «указатель на cvT2», в результате
static_cast<cvT2*>(static_cast<cvvoid*>(v))
если оба T1 и T2
Типы стандартного макета (3.9) и требования выравнивания T2
не более строгие, чем у T1. Преобразование значения типа «указатель на
T1 »к типу« указатель на T2 »(где T1 и T2 являются типами объектов и
где требования по выравниванию T2 не являются более строгими, чем требования
T1) и обратно в исходный тип возвращает исходное значение указателя.
Результат любого другого такого преобразования указателя не определен. «
Что это означает в вашем случае, требования по выравниванию выполняются, а результат не уточняется.
Поведение реализации в вашем примере является атрибутом endianness вашей системы, в этом случае ваш CPU немного порядковый.
О типе литья, когда вы бросаете int*
в char*
все, что вы делаете, говорит компилятору интерпретировать что cp
указывает на символ, поэтому он будет читать только первый байт и интерпретировать его как символ.
Преобразование между указателями само по себе всегда возможно, поскольку все указатели являются не чем иным, как адресами памяти, и любой тип в памяти всегда можно представить как последовательность байтов.
Но, конечно же, способ формирования последовательности зависит от того, как разложенный тип представлен в памяти, и это выходит за рамки спецификаций C ++.
Тем не менее, за исключением очень патологических случаев, вы можете ожидать, что представление будет одинаковым для всего кода, созданного одним и тем же компилятором для всех машин одной и той же платформы (или семейства), и вы не должны ожидать одинаковых результатов на разных платформах. ,
В общем, следует избегать выражения отношения между размерами шрифта как «предопределенного»:
в вашем образце вы предполагаете sizeof(int) == 4*sizeof(char)
Это не всегда так.
Но всегда верно, что sizeof (T) = N * sizeof (char), следовательно, все, что T всегда можно рассматривать как целое число char-s
Если у вас нет оператора приведения, то приведение просто говорит «увидеть» эту область памяти другим способом. Ничего особенного, я бы сказал.
Затем вы читаете побайтную область памяти; до тех пор, пока вы не измените его, это просто нормально. Конечно, результат того, что вы видите, во многом зависит от платформы: подумайте о порядке байтов, размеру слова, заполнении и так далее.
Просто измените порядок байтов, тогда он становится
00 00 01 ff
Что составляет 256 (01) + 255 (ff) = 511
Это потому что твой платфом немного порядковый.