2016-07-14 2 views
2

Подписанные целые числа представлены на x86 через два дополнения, где знаковый бит имеет значение -(2^N). Это приводит к типичному представляемому диапазону значений между -2^N и 2^N - 1 (например, -32768 - 32767).INT_MIN * -1 a-no-op на x86?

Мне было любопытно, что произойдет, если я возьму минимальное знаковое целочисленное значение в своей системе и умножьте его на -1, пытаясь «заставить» максимальное значение, большее, чем максимальное представляемое значение целых чисел со знаком в моей системе.

#include <stdio.h> 
#include <limits.h> 

int main(void){ 
    signed int x, y; 

    x = INT_MIN; 
    y = x * -1; 

    printf("%d\n%d\n", x, y); 

    return 0; 
} 

Это привело к следующему выходу:

# gcc -std=c89 -pedantic int_min_test.c 

# ./a.out 
-2147483648 
-2147483648 

Я ожидал целочисленное переполнение (в результате типичного значения опрокидывание), но это, кажется, как будто никакой операции не произошло относительно умножения x с -1.

Является ли умножение INT_MIN на -1 a no-op in x86?

+4

Не будет ли опрокидывание точно производить наименьшее возможное значение снова? –

+0

Это неопределенное поведение (для дополнения 2) - не имеет ничего общего с x86. –

+0

Что сказал @KerrekSB. Если вы хотите наблюдать это без UB, используйте только неподписанные типы. –

ответ

4

Использование GCC 4.8.5, линия y = x * -1; вычисляется с помощью следующей инструкции:

neg %eax 

Операция нег переворачивает биты слово, а затем добавляет 1. для 32 бит 2 в дополнение следующий результат:

0x80000000 # Start value 
0x7FFFFFFF # Flip bits 
0x80000000 # Add 1 

Как вы видите, компьютер делает именно то, что вы рассказываете это сделать. Это не является no-op, так как neg изменяет AF, CF, OF, PF, SF и ZF флаги. Это просто артефакт бинарного представления. Как утверждали другие, это просто неопределенное поведение.

4

Для C (на основе оригинальной тегирования вопроса и кода примера, написанного на C), это неопределенное поведение, поэтому нет, результат выражения C INT_MIN * -1 не является «не-оператором», это неопределенное поведение , На практике результат, вероятно, будет наблюдаться непоследовательно в зависимости от того, как вы его наблюдаете, возможно, даже временно несостоятельно. Не делай этого.

Если вы хотите спросить о инструкции x86 imul, это другой вопрос, и я считаю, (32-бит) imul из 0x80000000 по 0xffffffff производит 0x80000000. Но это не имеет ничего общего с тем, что вы должны ожидать увидеть в С.

+0

Да, меня интересует поведение кода операции x86. Я удалил тег 'C', чтобы предотвратить путаницу. –

+2

Простое изменение тегов на самом деле не помогает, когда код в вопросе равен C. Если вы хотите спросить об asm, используйте код примера asm. C не переводит непосредственно в asm. –

+0

Это хороший момент, с неопределенным поведением реализация C могла бы произвести любую последовательность операций по порядку. Я переписал лучший вопрос, чтобы правильно сфокусироваться на поведении x86. –

2

На процессоре x86 инструкции, которые будут умножать «int» на -1, будут иметь значение , если данный бит-шаблон 0x80000000 выдает тот же бит-образец в результате. Важно отметить, однако, что компилятор для x86 можно разделить на несколько категорий:

  1. О некоторых компиляторах, все подписанные целочисленные операции будут вести себя так, как будто по математике осуществляется с использованием родного операции точной длины, что эквивалентно выполнению операций с бесконечной точностью и добавлению или вычитанию в/из любого значения вне диапазона, для чего требуется кратное целочисленному модулю, чтобы привести его в диапазон.Такое поведение аналогично тому, что происходит с неподписанными типами, за исключением того, что диапазон отличается. На таких компиляторах -INT_MIN == INT_MIN.

  2. В некоторых компиляторах все подписанные целочисленные операции будут вести себя так, как если бы выполнялись с бесконечной точностью, но могут добавлять или вычитать из любого значения вне диапазона любое произвольное кратное целочисленному модулю, что, возможно, дает результат, ведет себя как число вне диапазона его типа. Это позволяет заменить выражение «x + 1> y» на «x> = y», а также разрешить перемещение кода за пределы циклов таким образом, чтобы это было невозможно, если переполнение должно было выполняться последовательно. В таких компиляторах -INT_MIN может равняться INT_MIN или может быть равен -(long)INT_MIN или любому другому значению, нижние 32 бита которого соответствуют INT_MIN. Некоторые из таких компиляторов будут усекать значения вне диапазона при хранении их в переменных, но некоторые могут этого не делать. Ключевой особенностью таких компиляторов является то, что код не должен предотвращать переполнение, если «лишние» биты результатов будут неактуальны в случаях, когда происходит переполнение (например, uint1=ushort1*ushort2;].

  3. Некоторые компиляторы будут использовать целочисленное переполнение в качестве основы для отрицания законов времени и причинности. При использовании таких компиляторов необходимо включить логику для предотвращения переполнения любой ценой независимо от того, генерирует ли этот код машины, что-то вносит в соответствие с требованиями к собранию программы.

Лично я считаю, что вторая форма семантики имеет наибольший смысл; он позволяет почти все полезные оптимизации, которые можно было бы сделать под третьим, и позволяет программистам активировать оптимизацию , что было бы невозможно под третьим. Если поведенческие гарантии в соответствии со второй формой будут достаточными, чтобы гарантировать, что программа будет соответствовать своим поведенческим требованиям, даже если происходит переполнение, принуждение программистов к обработке переполнений в любом случае сделает код менее эффективным. Хотя часто бывают случаи, когда проверка переполнения является хорошей идеей даже ценой снижения эффективности, я думаю, что есть что-то абсурдное в отношении «оптимизации» компиляторов, требующих, чтобы программисты писали менее эффективный код, чем это было бы в противном случае.