Возможный дубликат:
Когда Endianness становится фактором?
читая это Tuto Что касается endianess, я падаю на этот пример, где endianess имеет значение. Речь идет о записи символа *, заполненного 1 и 0. Затем он может быть преобразован в короткий, и результаты зависят от порядка байтов, малого или большого. Вот пример, цитируемый.
unsigned char endian [2] = {1, 0};
короткий х;x = *(short *) endian;
Каково будет значение х? Давайте посмотрим, что делает этот код.
Вы создаете массив из двух байтов, а затем приводите этот массив
два байта в одном коротком. Используя массив, вы в основном вынуждены
определенный порядок байтов, и вы увидите, как система обрабатывает
эти два байта. Если это система с прямым порядком байтов, 0 и 1
интерпретируется в обратном направлении и рассматривается как 0,1. Поскольку старший байт
0, это не имеет значения, и младший байт равен 1, поэтому x равен 1. На
с другой стороны, если это система с прямым порядком байтов, старший байт равен 1, а
значение х составляет 256.
мне интересно: когда вы создаете экземпляр массива с заданным количеством выделенных байтов памяти (здесь два байта), как можно выполнить преобразование в любой тип (short, int …), если массиву выделено число байтов, соответствующих этому байту? если для «содержания этого типа» было выделено недостаточно памяти, будет ли считываться следующий адрес памяти? например, если я хочу привести endian к long, это будет выполнено, прочитав четыре байта из начала endian, или это не получится?
Затем возникает вопрос о порядке байтов: это характерная особенность процессора в отношении привычки записывать байты в память, причем наиболее значимый байт находится в самой младшей ячейке памяти (старшем порядке) или в самой старшей ячейке памяти (младшем порядке). в этом случае был выделен массив с двумя однобайтовыми элементами. почему 1 считается самым значимым байтом?
Не забывайте, что компилятор будет писать только ассемблерный код. Если вы игнорируете все предупреждения о том, что компилятор, вы можете изучить код сборки, созданный компилятором и выяснить, что на самом деле происходит.
Я взял эту простую программу:
#include <iostream>
int main()
{
unsigned endian[2] = { 0, 0 } ;
long * casted_endian = reinterpret_cast<long*>( endian );
std::cout << *casted_endian << std::endl;
}
и я извлек этот код с помощью objdump
, Давайте расшифруем это.
804879c: 55 push %ebp
804879d: 89 e5 mov %esp,%ebp
804879f: 83 e4 f0 and $0xfffffff0,%esp
80487a2: 83 ec 20 sub $0x20,%esp
Эти строки являются просто прологом функции, игнорируйте их.
unsigned endian[2] = { 0, 0 } ;
80487a5: c7 44 24 14 00 00 00 movl $0x0,0x14(%esp)
80487ac: 00
80487ad: c7 44 24 18 00 00 00 movl $0x0,0x18(%esp)
80487b4: 00
Из этих двух строк вы можете видеть, что (0x14)% esp инициализируется с 0. Итак, вы знаете, что массив endian
находится в стеке, по адресу в регистре% ESP (указатель стека) + 0x14.
long * casted_endian = reinterpret_cast<long*>( endian );
80487b5: 8d 44 24 14 lea 0x14(%esp),%eax
LEA — это просто арифметическая операция. EAX теперь содержит% ESP + 0x14, который является адресом массива в стеке.
80487b9: 89 44 24 1c mov %eax,0x1c(%esp)
И по адресу ESP + 0x1c (где находится переменная casted_endian
) мы ставим EAX, поэтому адрес первого байта endian.
std::cout << *casted_endian << std::endl;
80487bd: 8b 44 24 1c mov 0x1c(%esp),%eax
80487c1: 8b 00 mov (%eax),%eax
80487c3: 89 44 24 04 mov %eax,0x4(%esp)
80487c7: c7 04 24 40 a0 04 08 movl $0x804a040,(%esp)
80487ce: e8 1d fe ff ff call 80485f0 <std::ostream::operator<<(long)@plt>
Затем мы готовим звонок оператору << с соответствующим аргументом без каких-либо дополнительных проверок. Вот и все, программа больше не будет проверять. Тип переменной совершенно не имеет отношения к машине.
Теперь две вещи могут произойти, когда operator<<
будет читать часть *casted_endian
которые не в массиве.
Либо его адрес находится на странице памяти, которая в данный момент отображается, либо нет. В первом случае operator<<
будет читать все, что по этому адресу без жалоб. Это, вероятно, напишет на экране что-то странное. Во втором случае ваша ОС будет жаловаться на то, что программа пытается прочитать что-то, что он не может прочитать, и провоцирует прерывание. Это знаменитая ошибка сегментации.
Если вы попытаетесь привести к размеру больше массива, вы получите неопределенное поведение. Вероятно, он попытается прочитать содержимое памяти, которая идет сразу после массива, но этот результат не гарантирован и не обязательно должен быть согласованным.
О, Боже. То, что я собираюсь сказать здесь, почему это работает на самый архитектуры, но я не могу сказать, насколько это на самом деле стандарт.
То, что вы делаете, это приведение массива endian
коротко. Теперь массивы — это в основном указатели, имя массива фактически содержит адрес первого элемента. Единственное реальное отличие состоит в том, что массивы содержат более полезные метаданные, а некоторые операции с массивами отличаются (sizeof
, например). Затем вы используете этот адрес (endian
) и создание short
указатель от него. Адрес памяти остается прежним, просто вы интерпретируете данные, на которые указывают по-разному. Затем вы разыменовываете этот указатель, чтобы вернуть значение и присвоить его x
,
Быстрое примечание стороны. Это может не работать на всех системах. В С, int
определяется как ширина, равная собственному размеру слова вашей архитектуры (4 байта на x86, 8 на x86_64). short
тогда определяется только как короче, чем int (или равно, если память работает правильно). По этой причине этот код не будет работать на 8-битных архитектурах. Чтобы это работало, размер целевого типа данных в байтах должен быть равен или меньше размера массива.
В равной степени, long
просто определено, чтобы быть длиннее, чем int
обычно 8 или 16 байт на x86 и x86_64 соответственно. В этом случае этот код будет работать на x86:
unsigned char endian[8] = {1,2,3,4,5,6,7,8};
long x = *(long*)endian;
В любом случае, порядковый номер процессора полностью зависит от процессора. x86 является прямым порядком байтов (и в основном начал конвенцию устройств LE, IIRC). SPARC с прямым порядком байтов (до 9, который может быть и тем и другим). ARM и MIPS также можно настраивать, а Microblaze зависит от используемой шины (AXI или PLB). В любом случае, порядковый номер не ограничивается только процессорами, он также является проблемой при взаимодействии с оборудованием или другими компьютерами.
Для вашего последнего вопроса, самый значимый байт называется так, потому что значение представляет больше, чем наибольшее значение, которое могут представлять младшие байты. В случае 16-битного слова младший значащий байт может представлять 0-255, а старший значащий байт — 256-65535.
В любом случае, если вы не занимаетесь системным программированием низкого уровня (а я имею в виду прямое изменение памяти) или пишете протоколы связи, вы никогда не будете Когда-либо нужно беспокоиться о порядке байтов
unsigned char endian[2] = {1, 0};
short x;
x = *(short *) endian;
Этот код имеет неопределенное поведение. Результат может быть x
установите значение 1, 256, 4000, иначе программа может аварийно завершить работу или что-то еще может произойти по закону. Это имеет место даже без учета того, является ли массив достаточно большим для типа, к которому он приведен.
Вот переписать код, чтобы сделать его легальным и сделать то, что задумал автор.
unsigned char endian[sizeof(short)] = {1};
short x;
std::memcpy(&x, endian, sizeof(short));
Если бы вы должны были написать код, который пытался получить int
из этого массива он получит доступ за допустимые границы массива, и вы снова столкнетесь с неопределенным поведением; может произойти все, что угодно.
в этом случае был выделен массив с двумя однобайтовыми элементами. почему 1 считается самым значимым байтом?
(Я предполагаю, что вы хотите спросить, почему endian[1]
говорят, что он содержит самый значимый байт.)
Потому что в этом примере система имеет младший порядок байтов и, как вы говорите, определение байтов с прямым порядком байтов — это самый старший байт в ячейке памяти с самым высоким адресом. endian[1]
имеет более высокий адрес, чем endian[0]
так endian[1]
будет содержать самый значимый байт.