Идентификация подписанных и неподписанных значений в сборке

Я всегда нахожу это запутанным, когда смотрю на разборку кода, написанного на C/С++.

Существует регистр с некоторым значением. Я хочу знать, представляет ли он подписанный номер или номер без знака. Как я могу это узнать?

Я понимаю, что если это целое число со знаком, MSB будет установлен, если он отрицательный и не установлен, если он положительный. Если я нахожу, что это целое число без знака, MSB не имеет значения. Правильно ли это?

Несмотря на это, это, похоже, не помогает: мне все равно нужно определить, подписано ли целое число, прежде чем я смогу использовать эту информацию. Как это можно сделать?

Ответ 1

Лучше всего искать сравнения и связанные действия/использование флагов, например, ветку. В зависимости от типа компилятор генерирует другой код. Поскольку большинство (соответствующих) архитектур предоставляют флаги для обработки подписанных значений. Принимая x86, например:

jg, jge, jl, jle = branch based on a signed comparison (They check for the SF flag)
ja, jae, jb, jbe = branch based on a unsigned comparison (They check for the CF flag)

Большинство инструкций по процессору будут одинаковыми для операций с подписью/без знака, потому что в настоящее время мы используем представление Two's-Complement. Но есть исключения.

В качестве примера возьмем правое смещение. С неподписанными значениями на X86 вы должны использовать SHR, чтобы сдвинуть что-то вправо. Это добавит нули на каждый "вновь созданный бит" слева.

Но для подписанных значений обычно используется SAR, поскольку он расширяет MSB во все новые биты. Это называется "расширением знака" и снова работает только потому, что мы используем Two's-Complement.

И последнее, но не менее важное: существуют разные инструкции для умножения/деления с подписью/без знака.

imul+idiv = signed
mul+div = unsigned

Как отмечено в комментариях, imul является особым случаем, поскольку он также может использоваться для беззнакового умножения. Единственное различие будет в установленных флажках. Поэтому не слишком доверяйте коду, если вы видите imul со значением, это будет зависеть от обстоятельств.

Кроме того, инструкция NEG обычно используется только для подписанных значений, потому что это двухкомпонентное отрицание.

Ответ 2

В общем, вы не сможете. Многие вещи, которые происходят с интегральными значениями, происходят одинаково для подписанных или неподписанных значений. Назначение, например. Единственный способ сказать, если код выполняет арифметику. Вы абсолютно не можете сказать, глядя на стоимость; все возможные битовые шаблоны действительны в любом случае.

Ответ 3

В большинстве процессоров (по крайней мере, те, которые используют две дополнения математики), нет никаких неотъемлемых признаков для целых чисел, хранящихся в регистры или память. Интерпретация зависит от используемых инструкций. Краткое описание:

  • Сложение и вычитание производят точно такие же битовые шаблоны для подписанных и неподписанных чисел, поэтому обычно нет подписанного сложения или вычитания. (Hovewer, MIPS имеет отдельные инструкции, которые вызывают ловушку, если операция переполняется).

  • Разделение и умножение дают разные результаты для подписанных и неподписанных чисел, поэтому, если процессор поддерживает его, они попадают парами (x86: mul/imul, div/idiv).

  • условные ветки также могут различаться в зависимости от интерпретации результата сравнения (обычно реализуемого как вычитание). Например, на x86 существует jg для подписанных больше и ja для неподписанных выше.

Обратите внимание, что числа с плавающей запятой (в формате IEEE в аренду) используют явный бит знака, поэтому указанное выше не относится к ним.

Ответ 4

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

Например, в

add eax, edx    ; eax = 0xFFFFFFF0, edx = 100

eax, вероятно, содержит подписанную переменную. Здесь нет никаких гарантий, но никаких гарантий нет - всегда есть вероятность, что код просто ошибочен. Код с (преднамеренным или непреднамеренным) неподписанным переполнением в нем существует, но гораздо более вероятно, что он на самом деле должен был интерпретироваться как подписанный non-overflow.