#c #assembly
#c #сборка
Вопрос:
Я пытаюсь разобраться в сборке, но есть одна, вероятно, очень простая вещь, которую я не понимаю.
Рассмотрим следующий простой пример
long long * values = new long long[2];
values[0] = 10;
values[1] = 20;
int j = -1;
values[j 2] = 15; // xxxxxxx
Теперь последняя строка (отмеченная xxxxxx) разбирается на:
000A6604 mov eax,dword ptr [j]
000A6607 mov ecx,dword ptr [values]
000A660A mov dword ptr [ecx eax*8 10h],0Fh
Первый вопрос: что на самом деле хранится в eax и ecx, это фактические значения (т.Е. -1 для «j» и два длинных длинных значения 10 и 20 для «значений»), или это просто адрес памяти (например, что-то вроде amp;p , amp;values ), указывающий на некоторыеместо, где хранятся значения?
Второй вопрос, я знаю, что должна делать третья строка, но я не совсем уверен, почему это действительно работает. Насколько я понимаю, он копирует значение 0x0F в указанную ячейку памяти. Ячейка памяти — это, по сути, местоположение первого элемента, хранящегося в ecx, плюс размер long long в байтах (= 8) * значение eax (которое равно j, поэтому -1) — плюс общее смещение в 16 байт (в 2 раза больше размера long long). Чего я не понимаю: в этом выражении ecx представляется адресом памяти, в то время как eax представляется значением (-1). Как это возможно? Поскольку они были определены практически одинаково, не должны ли eax и ecx либо содержать адреса памяти, либо оба значения?
Спасибо.
Комментарии:
1. Адрес памяти и значение — это просто биты. Разница только в том, что представляют эти биты.
2. Что касается первого вопроса: он загружает значения
j
иvalues
соответственно. Значениеvalues
, в свою очередь , является адресом фрагмента памяти. Написать что-то подобноеmov ecx, OFFSET values
было бы похоже на получение адреса указателя в C, который дает вам указатель на указатель.3. Обратите внимание на это
j
, иvalues
в вашем коде C тоже есть разные типы.4. Ах, конечно, это имеет смысл. j = -1, но значения = адрес памяти. Я путал значения со значениями *. Хорошо, я думаю, это объясняет оба вопроса, большое вам спасибо!
Ответ №1:
eax
и ecx
являются регистрами — первые две инструкции загружают в эти регистры значения, используемые при вычислении, т.Е. j
и values
(где values
означает базовый адрес массива с этим именем).
Я знаю, что должна делать третья строка, но я не совсем уверен, почему это действительно работает
Инструкция mov dword ptr [ecx eax*8 10h],0Fh
означает перемещение значения 0Fh (т.Е. 15 десятичных знаков) в местоположение ecx eax*8 10h
. Чтобы понять это, рассмотрим каждую часть:
-
ecx
является базовым адресомvalues
массива -
eax
является ли значение равнымj
, то есть -1 -
eax*8
j
преобразуется в смещение в байтах — размер along long
равен 8 байтам -
eax*8 10h
10h — это 16 десятичных знаков, то есть 2 * 8, поэтому оноj 2
преобразуется в смещение в байтах -
ecx eax*8 10h
добавляет это окончательное смещение к базовому адресу массива, чтобы определить местоположение, в котором будет храниться значение 15
Комментарии:
1. Очень хороший ответ, спасибо! И последний вопрос: как он «знает», что j (или eax, соответственно) подписан? Определение j было «mov dword ptr [j],0FFFFFFFFh». Таким образом, 0xFFFFFFFF равняется 4 294 967 295, если интерпретируется как число без знака (и действительно, это то, что сообщает мне окно просмотра в VC). Но я предполагаю, что 4,294,967,295 * 8 вызовет переполнение или даст что угодно, но не -8, как предполагалось. Итак, как это работает ?!
2.
0xFFFFFFFF * 8
is0x7FFFFFFF8
, который действительно слишком велик, чтобы поместиться в 32-разрядное число (т.Е. Указатель), поэтому используются только младшие значащие 32 бита. Это оставляет нас0xFFFFFFF8
, т.е. -8 десятичных знаков. Добавьте2*8
к этому, и вы получите0x00000008
, какую сумму нужно добавитьvalues
, чтобы получить адресvalues[-1 2]
.3. Отлично, понял. Однако откуда он знает, что 0xFFFFFFF8 означает -8, а не 4 294 9672,88? Например. подписанный int a = -1 = 0xFFFFFFFF и unsigned int b = 4294967295 = 0xFFFFFFFF . Но доступ к значениям [a 1] и значениям [b 1] выдает один и тот же код сборки; так как же он узнает, обращаться ли к элементу 0 или элементу 4 294 967 296?
4. Процессор не «знает» — это всего лишь биты. Компилятор (точнее, люди, которые написали компилятор) знает, как использовать дополнительную арифметику 2 для реализации отрицательных чисел. Возможно, вам придется поиграть с некоторыми примерами, чтобы действительно понять. Во-первых, убедитесь, что вы знаете, как использовать дополнение 2 (т. Е. инвертировать все биты и добавить 1). Пример:
0x12345678 - 0x8
==0x12345678 0xFFFFFFF8
==0x112345670
, это только0x12345670
в том случае, если вы ограничены младшими значащими 32 битами.5. Кажется, что не хватает ‘mov dword ptr [ecx eax* 8 14h],00h’, чтобы обнулить верхнюю половину значений [j 2], поскольку это длинное длинное (64-битное или qword).