2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему
 
 Арифметический сдвиг влево более чем на 31 разряд
Сообщение19.12.2005, 14:42 


27/11/05
183
Северодонецк
Предполагается, что следующая программа
выполняется в Microsoft Visual C++ 6.0:

#include <stdio.h>

int n1 = 31, n2 = 32, n3 = 33;

int main(int argc, char **argv)
{
printf("a) %x %x %x\n", 5<<n1, 5<<n2, 5<<n3);
printf("b) %x %x %x\n", 5<<31, 5<<32, 5<<33);
return(0);
}

и печатает результат:

a) 80000000 5 a
b) 80000000 0 0

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

a) 80000000 0 0
b) 80000000 0 0

Этот же вопрос можно отнести и к компиляторам C++ других
платформ, у которых разрядность int равна 32 бита.

 Профиль  
                  
 
 
Сообщение19.12.2005, 17:59 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Вопрос сразу - а в каком режиме компилируется программа (C или C++)? MS по умолчанию определяет по расширению файла.

 Профиль  
                  
 
 
Сообщение19.12.2005, 18:30 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
to bekas:
Пробовали в ассемблерный код это перевести? Мож, тогда все прояснится? Я откомпилировал GCC, watcom C/C++ (из состава mingw) - результат тот же, что и у Вас. При обоих режимах (С и С++) получается одно и то же. Ща в асменный код переведу и позыркаю :)

 Профиль  
                  
 
 
Сообщение19.12.2005, 18:37 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Я поспешил задать вопрос. Согласно стандарту обоих языков, поведение оператора << не определено, если величина сдвига больше либо равна разрядности (а также если левый операнд отрицательный). В случае явного значения обеих величин, MS использует константные вычисления в компиляторе. В компиляторе (по-видимому) все целые значения хранятся и обрабатываются как 64битные, и лишь потом приводятся к нужной разрядности. В случае же вычисления в программе MS вполне резонно использует команду Intel'овского процессора, которая игнорирует биты старше 5го (32рарядный сдвиг).

Более точно сей тест выглядит таким образом:

#include <stdlib.h>

void foo(int n5, int n31, int n32, int n33) {
    printf("%d << %d: %x %x %x %x", n5, n31, 5 << 31, 5 << n31, n5 << 31, n5 << n31);
    printf("%d << %d: %x %x %x %x", n5, n32, 5 << 32, 5 << n32, n5 << 32, n5 << n32);
    printf("%d << %d: %x %x %x %x", n5, n33, 5 << 33, 5 << n33, n5 << 33, n5 << n33);
}

int main(int argc, char** argv) {
    foo(5, 31, 32, 33);
}

Отделение печати в функцию необходимо, чтобы сбить оптимизацию (параметры не трассируемы). Локальные переменные легко трассируюся компилятором и оптимизируются во время компиляции.

 Профиль  
                  
 
 
Сообщение19.12.2005, 18:49 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
Незванный гость, Вы как всегда правы - в случае констант (при втором вызове printf) компилятор использовал константные вычисления. Это хорошо видно из асменного кода (компилятор GCC, у MS я опции перевода в асм не нашел)

код: [ скачать ] [ спрятать ]
Используется синтаксис ASM
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        call    __alloca
        call    ___main
        movl    $5, %eax
        movb    _n3, %cl
        sall    %cl, %eax
        pushl   %eax
        movl    $5, %eax
        movb    _n2, %cl
        sall    %cl, %eax
        pushl   %eax
        movl    $5, %eax
        movb    _n1, %cl
        sall    %cl, %eax
        pushl   %eax
        pushl   $LC0
        call    _printf
        addl    $16, %esp
        pushl   $0
        pushl   $0
        pushl   $-2147483648
        pushl   $LC1
        call    _printf
        addl    $16, %esp
        movl    $0, %eax
        leave
        ret



Видно невооруженным глазом, что во втором случае компилер сразу грузит в качестве параметров нули, а в первом что-то пытается вычислять(и, похоже, команда sall дает не совсем те результаты, что ожидал bekas)!

 Профиль  
                  
 
 
Сообщение19.12.2005, 19:00 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Позвольте обратить Ваше внимание на следующий фрагмент:
Код:
   movl   $5, %eax
   movb   _n1, %cl
   sall   %cl, %eax
Id est, GCC грузит второй операнд как байт! Он знает, что только 5 бит использутся, и не утруждает себя и процессор грузить слово.

 Профиль  
                  
 
 
Сообщение19.12.2005, 19:12 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
:) Хотя я слышал, что иногда лучше все-таки оперировать словами (а не байтами). Вроде как скорострельность будет больше, если данные в памяти выровнены по границам слов, и грузятся соответственно тоже словами.

 Профиль  
                  
 
 
Сообщение19.12.2005, 19:54 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Sanyok писал(а):
:) Хотя я слышал, что иногда лучше все-таки оперировать словами (а не байтами). Вроде как скорострельность будет больше, если данные в памяти выровнены по границам слов, и грузятся соответственно тоже словами.

Это очень тонкий вопрос. И самое главное - а хотите ли Вы оптимизировать программу на таком уровне. Главная проблема - это слишком многое зависит от конкретного процессора и конкретного железа (контроллера памяти и самой памяти). Точное априорное предсказание поведения возможно, но требует просто колосальных трудозатрат. Motorola, например, публикует схемы выполнения команды в процессоре с правилами их наложения, et cetera. И, несмотря на их подробность, я не уверен, что они достаточно полны и точны.

Можно пойти по пути эксперимента. Но тогда Вы оптимизируете программу под свое железо, и, в общем-то, вслепую. Факторы же влияющие на быстродействие могут быть зело неочевидны. Я однажды провел вечер гоняя тесты (лучше бы голубей гонял!), чтобы убедиться, что результат существенно зависит от выравнивания самой программы (а не данных, в моем случае). Мне повезло, ибо железо, под которое я гнал было строго однородно - это далеко не всегда так.

Мой вывод такой - оптимизация всей программы того не стоит. Надо писать аккуратно и грамотно. Если этого мало - найти 10% кода, тратящих 90% времени, и оптимизировать их. В остальном довериться авторам компилятора и библиотеки. (Я видел как-то серьезный memcpy() для Intel'а - с опережающей закачкой в кэш процессора, оптимизацией размера обращения к памяти, и прочее. Ну их на фиг. Я лучше буду благодарным пользователем такой библиотеки - до тех пор, пока сам не сяду писать библиотеку. Или не прижмет крепенько.)

Помимо всего прочего, оптимизированный код хуже читается, и дороже в сопровождении, что немаловажно.

 Профиль  
                  
 
 
Сообщение19.12.2005, 19:56 


27/11/05
183
Северодонецк
to Sanyok:

Чтобы получить ассемблерный код для MS, необходимо:

1) По Alt+F7 открыть окно 'Project Settings'
2) Выбрать закладку 'C/C++'
3) В выпадающем списке 'Category' выбрать 'Listing Files'
4) В выпадающем списке 'Listing file type' выбрать подходящую
опцию, например, 'Assemble,Machine Code,and Source'
5) Перекомпилиривоть проект с предварительным Clean

В соответствующем каталоге, например, Release, для каждого '.cpp'
появится символьный файл '.cod', содержащий ассемблерные команды.

to незванный гость:

(а также если левый операнд отрицательный). - имелся в виду правый операнд?

 Профиль  
                  
 
 
Сообщение19.12.2005, 20:08 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
Вот, то что MS наваял:
код: [ скачать ] [ спрятать ]
Используется синтаксис ASM
_main   PROC NEAR
; File shift.c
; Line 6
        push    ebp
        mov     ebp, esp
; Line 7
        mov     eax, 5
        mov     ecx, DWORD PTR _n3
        shl     eax, cl
        push    eax
        mov     edx, 5
        mov     ecx, DWORD PTR _n2
        shl     edx, cl
        push    edx
        mov     eax, 5
        mov     ecx, DWORD PTR _n1
        shl     eax, cl
        push    eax
        push    OFFSET FLAT:$SG343
        call    _printf
        add     esp, 16                                 ; 00000010H
; Line 8
        push    0
        push    0
        push    -2147483648                             ; 80000000H
        push    OFFSET FLAT:$SG344
        call    _printf
        add     esp, 16                                 ; 00000010H
; Line 9
        xor     eax, eax
; Line 10
        pop     ebp
        ret     0
_main   ENDP



Как видите - один черт, что тот, что другой. Все дело в том, как процессор выполняет комаду сдвига.

 Профиль  
                  
 
 
Сообщение19.12.2005, 20:08 
Заслуженный участник
Аватара пользователя


17/10/05
3709
bekas писал(а):
(а также если левый операнд отрицательный). - имелся в виду правый операнд?

Вы правы. Я невнимательно читал стандарты (и невнимательно писал - извините, пожалуйста). Интересно, что стандарт не определяет поведения сдвига влево для целых со знаком, только для беззнаковых типов. Сдвиг же вправо определен, и в случае отрицальных чисел оставлен на усмотрение реализации.

 Профиль  
                  
 
 
Сообщение20.12.2005, 23:59 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Маленькое уточнение - в С-99 уточнено, что для отрицательных значений результат сдвига влево не определен.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 12 ] 

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: Dmitriy40


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group