GCC переполнение влево - PullRequest
       5

GCC переполнение влево

21 голосов
/ 06 октября 2010

Следующая небольшая программа очень неудобна с использованием GCC версии 4.2.1 (Apple Inc., сборка 5664) на Mac.

#include <stdio.h>

int main(){
        int x = 1 << 32;
        int y = 32;
        int z = 1 << y;
        printf("x:%d, z: %d\n", x, z);
}

Результат x:0, z: 1.
Есть идеи, почему значения x и z различны?
Большое спасибо.

Ответы [ 4 ]

17 голосов
/ 06 октября 2010

Краткий ответ: процессор Intel маскирует число сдвигов до 5 бит (максимум 31).Другими словами, фактически выполненное смещение равно 32 и 31, что равно 0 (без изменений).

Тот же результат появляется при использовании gcc на 32-разрядном ПК с Linux.

Я собралболее короткая версия этой программы, потому что я был озадачен тем, почему сдвиг влево на 32 бита должен вообще приводить к ненулевому значению:

int main(){
    int y = 32;
    unsigned int z = 1 << y;
    unsigned int k = 1;
    k <<= y;
    printf("z: %u, k: %u\n", z, k);
}

.. используя команду gcc -Wall -o a.s -S deleteme.c (комментарии мои)

main:
leal    4(%esp), %ecx
andl    $-16, %esp
pushl   -4(%ecx)
pushl   %ebp
movl    %esp, %ebp
pushl   %ecx
subl    $36, %esp
movl    $32, -16(%ebp)  ; y = 32
movl    -16(%ebp), %ecx ; 32 in CX register
movl    $1, %eax        ; AX = 1
sall    %cl, %eax       ; AX <<= 32(32)
movl    %eax, -12(%ebp) ; z = AX
movl    $1, -8(%ebp)    ; k = 1
movl    -16(%ebp), %ecx ; CX = y = 32
sall    %cl, -8(%ebp)   ; k <<= CX(32)
movl    -8(%ebp), %eax  ; AX = k
movl    %eax, 8(%esp)
movl    -12(%ebp), %eax
movl    %eax, 4(%esp)
movl    $.LC0, (%esp)
call    printf
addl    $36, %esp
popl    %ecx
popl    %ebp
leal    -4(%ecx), %esp
ret

Хорошо, так что это значит?Меня озадачивает эта инструкция:

sall    %cl, -8(%ebp)   ; k <<= CX(32)

Очевидно, что k означает , смещенное влево на 32 бита.

Вы меня поняли - оно использует sallинструкция, которая является арифметическим сдвигом .Я не знаю, почему поворот на 32 приводит к повторному появлению бита в исходном положении.Моя первоначальная гипотеза состояла бы в том, что процессор оптимизирован для выполнения этой инструкции за один такт - это означает, что любой сдвиг более чем на 31 будет рассматриваться как неважно.Но мне любопытно найти ответ на этот вопрос, потому что я ожидаю, что вращение должно привести к тому, что все биты будут падать с левого конца типа данных.

Я нашел ссылку на http://faydoc.tripod.com/cpu/sal.htmчто объясняет, что счетчик сдвига (в регистре CL) маскируется до 5 битов.Это означает, что если вы попытаетесь сдвинуться на 32 бита, фактический сдвиг будет выполнен с нулевыми битами (т.е. без изменений).Там ответ!

16 голосов
/ 06 октября 2010

Если ваши ints 32 бита или меньше, поведение не определено ... и неопределенное поведение не может быть объяснено .

Стандарт гласит:

6.5.7 / 3 [...] Если значение правого операнда отрицательно или больше или равно ширине повышенного левого операнда, поведение не определено.


Вы можете проверить размер int ширина бит, например, с помощью:

#include <limits.h>
#include <stdio.h>
int main(void) {
    printf("bits in an int: %d\n", CHAR_BIT * (int)sizeof (int));
    return 0;
}

И вы можете проверить ширину int (могут быть биты заполнения)например, с помощью:

#include <limits.h>
#include <stdio.h>
int main(void) {
    int width = 0;
    int tmp = INT_MAX;
    while (tmp) {
        tmp >>= 1;
        width++;
    }
    printf("width of an int: %d\n", width + 1 /* for the sign bit */);
    return 0;
}

Стандарт 6.2.6.2/2: Для целочисленных типов со знаком биты представления объекта должны быть разделены на три группы: биты значения, биты заполнения изнак бит.Там не должно быть никаких битов заполнения;должен быть ровно один знаковый бит

6 голосов
/ 06 октября 2010

Стандарт C99 говорит, что результат смещения числа на ширину в битах (или больше) операнда не определен. Почему?

Ну, это позволяет компиляторам создавать наиболее эффективный код для конкретной архитектуры. Например, команда сдвига i386 использует поле шириной пять бит для количества бит, на которое сдвигается 32-битный операнд. Стандарт C99 позволяет компилятору просто взять нижние пять битов счетчика сдвигов и поместить их в поле. Понятно, что это означает, что сдвиг в 32 бита (= 100000 в двоичном виде), следовательно, идентичен сдвигу 0, и поэтому результатом будет левый операнд без изменений.

Другая архитектура ЦП может использовать более широкое битовое поле, скажем, 32-битное. Компилятор все еще может поместить счетчик сдвигов непосредственно в поле, но на этот раз результат будет равен 0, поскольку сдвиг в 32 бита сместит все биты из левого операнда.

Если C99 определил одно или другое из этих поведений как правильное, либо компилятор для Intel должен установить специальную проверку слишком большого количества смен, либо компилятор для не i386 должен замаскировать счет смены.

Причина, по которой

   int x = 1 << 32;

и

   int z = 1 << y;

дают разные результаты, потому что первое вычисление является константным выражением и выполняется полностью компилятором. Компилятор должен вычислять константные выражения, используя 64-битную арифметику. Второе выражение вычисляется кодом, сгенерированным компилятором. Поскольку тип y и z равен int, код генерирует вычисление, используя 32-разрядные целые числа (int равен 32 битам на i386 и x86_64 с gcc в Apple).

0 голосов
/ 07 февраля 2014

По-моему "int x = y << 32;" не имеет смысла, если sizeof (int) == 4. </p>

Но у меня была похожая проблема с:

long y = ... длинный x = y << 32; </p>

Где я получил предупреждение «предупреждение: счетчик смещения влево> = ширина типа», хотя sizeof (long) было 8 для целевой цели Я избавился от предупреждения, сделав это вместо:

long x = (y << 16) << 16; </p>

И это, похоже, сработало.

На 64-битной архитектуре предупреждения не было. На 32-битной архитектуре было.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...