Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Два парадокса в программах на языке C

Anna | 24.06.2014 | нет комментариев

Хочу рассказать о 2-х странностях, с которыми мне пришлось столкнуться, программируя вычислительные алгорифмы на языке C.

Выходит, первое непредвиденное поведение для некоторых программистов. Вот крошечная прога.

#include <stdio.h>
int main()
{
    unsigned char a = 1, b;
    b = ~a >> 1;
    printf("%un", b);
    return 0;
}

Разберем ее. Поразрядная операция ~ инвертирует состояние всякого бита байта a, в тот, что первоначально записана единица. В итоге обязаны получить 11111110b, то есть 254. Сдвигая данный байт вправо на один бит, обязаны получить 127. Впрочем код, тот, что дает, скажем, компилятор gcc, выводит в консоль число255?!

Вначале я подумал о том, что дело в приоритете — внезапно у компилятора приоритет операций «косячит»? То есть словно бы вначале делается сдвиг, а потом — инверсия (а что, разумно…). Так в чем же дело?

Позже некоторых раздумываний мне пришла в голову иная догадка о том, что при инверсии байт приводится к слову (ну, либо к двойному слову), а потом теснее инвертированное слово сдвигается. Вот откуда и получается 255 — старшие биты в слове нули, инвертируя их, имеем единицы. После этого, делая сдвиг слова вправо на один бит, в его младшем байте во всех битах будут находиться единицы.

Это и подтверждает дальнейший код.

#include <stdio.h>
int main()
{
    unsigned char a = 1, b;
    b = (unsigned char)~a >> 1;
    printf("%un", b);
    return 0;
} 

Сейчас мы получаем верный итог. Но окончательно удостоверился в этом, дизассемблировав ELF-файл, тот, что дает gcc. Приведу фрагмент полученного ассемблерного кода.

mov [ebp var_6], 1
movzx eax, [ebp var_6]
not eax
sar eax, 1
mov [ebp var_5], al

Вначале через стек единица попадает в 32-х разрядный регистр eax. Дальше он инвертируется, а потом сдвигается. Итог достается из младшей части регистра ax — регистра al. Это и оправдывает мою догадку — единицы, которые были за необходимым байтом, при сдвиге двойного слова в него попали.

Как потом выяснилось, эта обстановка именуется Integer Promotion и описывается в п. 6.3.1.1 эталона C99. Загрузить его дозволено отсель www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

Второе непредвиденное поведение для некоторых программистов связано с вещественными числами. Имеем дальнейший код.

#include <stdio.h>
int main()
{
    float a = 1.005, b = 1000;
    int c = a*b;
    printf("%dn", c);
    return 0;
}

Компилируя его gcc 4.1.1, получаю 1004. Вновь вопрос — откуда берется необычный итог? Даже это

int c = (float)(a*b);

также не дает положительного итога.

Полазив по эталону C89, оказалось, что он ничего не регламентировал о методах работы с вещественными числами. чай, когда возникло растяжение SSE, компиляторы начали считать смешанным образом — как посчитается стремительней: что-то на FPU, что-то на SSE. В новом эталоне C99 возникла некоторая определенность. Компилятор должен выставить значение макроса FLT_EVAL_METHOD (заголовочный файлfloat.h) в 012 для метода, которым он считает. Выходит, 0 — все считать так, как написано; 1 — float на самом деле считать в double и после этого конвертировать обратно во float; 2 — все считать в long double, конвертируя во float либо double в конце вычислений соответственно.

Сейчас, Дабы принудить считать прогу так, как нужно, необходимо собирать ее

gcc proga.c -msse

Только позже этого у меня в консоль вывелось число 1005. При этом выяснилось, что моя версия компилятора gcc не поддерживает макрос FLT_EVAL_METHOD. Кстати, с double gcc даёт код, выводящий1004. Только Intel C 9.0 сделал типичный код с double, но когда я записал

int c = (float)(a*b);

(тут a и b теснее типа double). Без приведения типа код и там даёт 1004.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB