Понемногу изучаю С, проникаюсь философией. Встречаю много логически не объяснимых вещей. Например, сабж. Стандарт говорит поведение в случае переполнения определяется реализацией компилятора. И тут мне стало совсем интересно. MSVC не представляет средств для отлова данного происшествия вообще нет. Те компилятор моча делает варп араунд 0. И всё. На сайте Микрософта тихо как в болоте. На блоге Микрософта посвещенном безопасности(http://blogs.msdn.com/michael_howard/default.aspx) есть рекомендации использовать, для безопасного сложения и тп, кучу нелепого кода. Для с++ спец класс, для с - библиотека. На интеле вообще ничего не нашел, кроме как тем что искал плохо объяснить немогу. Не может быть чтобы сам производитель процессоров не представил какихто средств для поимки такого случая. У gcc есть спец ключики которые позволяют включать такие проверки, однако рядом с ними написано что они толи не работают вообще, толи работают непонятным образом. Linux сейчас качается, попробую. Однако интересует Win. В старое доброе время на противной Delphi выскакивал простой и ясный эксепшн. Странно что на С++ такого нет. Я стал искать, в SEH обнаружил exception_int_overflow. Попробовал, не ловит. Если заменить на деление на 0 то ловит. А переполнение нет. Почему так? Да я понимаю что с одной стороны переполнение в int не всегда ошибка и сброс на 0 это то что надо. Но ведь это скорее исключение чем правило. Да конечно это очень снизит скорость кода, в каждое сложение jc jo into... Но ведь можно было реализовать как опцию. Код (Text): 1: public static int binarySearch(int[] a, int key) { 2: int low = 0; 3: int high = a.length - 1; 4: 5: while (low <= high) { 6: int mid = (low + high) / 2; 7: int midVal = a[mid]; 8: 9: if (midVal < key) 10: low = mid + 1 11: else if (midVal > key) 12: high = mid - 1; 13: else 14: return mid; // key found 15: } 16: return -(low + 1); // key not found. 17: } Пишут что данный код жил и работал внутри джава машины более 9 лет... Может быть с точки зрения философии С это будет не кошерно? Однако все процессоры, да и многие микроконтроллеры на которые можно писать С код, с незапамятных времен содержат флаги переноса. Те переносимость не пострадает. Почему так? В чом великая истина. Подскажите что я упускаю... Предположим. Код (Text): int16 zarpalata, premia, naruki; premia = 16000; zarpalata = 16000; naruki = premia + zarpalata; Чертовски обидно получается!
МПМ Зачем это тебе? Контроль переполнения имхо нужен для реализации длинной арифметики. В обычном коде тупо выбирают тип соответствующий размеру данным. Чтобы данные были бесконечно большими это надо очень сильно постараться, зарплата не бывает безразмерной. То что произошло переполнение можно проверить и проще, например тем что результат стал меньше или остался равным первому аргументу. Пока не встречал, чтобы это было нужным. Чтобы компилятор каждый раз делал проверку и что-то мутил, для чего?
naruki = 32000 чего обидного? а вот 16000+17000=-32536 (вот тут обидно, что должен остался) в случае переносов си любит баловаться с командами типа setc и cmovc, а вот тут ему придется покрутиться. для setc нужен 8 битный регистр, а для cmovc 32 битный. Но компиляторы Си просто не любят пользоваться более чем 3 регистрами (eax, ecx, edx). Все остальное сгребают в память, и начинаются сложности (cmovc [mem16/32], reg16/32 возможно, но данные надо заранее получить в регистре, одном из трех). При сложение скорее всего придется сгенерировать что-то типа Код (Text): xor edx, edx //2 add eax, ecx //2 setc dl //2 //или add edx, ecx //2 salc // 1 cbw // 1 cwde // 1 скорее всего на месте одного из регистров может оказаться ячейка памяти, но это сути не поменяет. И не сильно таки замедлит код, а вот с переносимостью будут проблемы код скрорее всего преобразится в это Код (Text): xor eax, eax // 2 add edx, ecx // 2 adc eax, 0 // 5 команда же adc edx, 0 занимает 6 байт //или но менее предпочтительнее из-за условного перехода работающего как правило, а не исключение add edx, ecx // 2 jnc @f // 2 mov eax, 1 // 5 команда же mov edx, 1 занимает 6 байт @@:
max7C4 С чем? Ради того что кто-то с чем-то не сталкивался, городить огород? Для длинной арифметики ещё можно результирующий тип брать большей разрядностью чем операнды. А Delphi для бизнес приложений, там безопасность прежде всего.
Чушь. Базовые инструкции работают с числами точно так же - 2's compliment arithmetic была придумана именно для этого. Хочшь отдать бит под знак - пожалуйста. Не хочешь - не надо. Overflow и underflow с целочисленнымми аргументами работали так как есть с самого начала эры транзисторов. В 32 битах: 0xf - 0x10 : результат больше чем первое слагаемое. 0xffffff00 + 0x101 : результат меньше чем первое слагаемое.
Был сонный, числа подобрал немного неверно. Будет красивее с 16к и 17к, полностью согласен. max7C4 Почему будут проблемы с переносимостью? Спасибо за ответы всем. Я так понимаю что это поведение компилятора считается верным и в случае сложения 2х int32, логичнее положить результат в int64 чем проверить флаги CPU? Взял свою старенькую книжку про Паскаль, там написано про переполение. Ни в одной книге про С и С++ этого нет... Те нет даже упоминания.
МПМ Я вот пишу на Паскале (Delphi). И за всю жизнь ни разу не юзал эту опцию компилятора (которая вкл. данные проверки). Всё время сам if-ы расставляю. Так что приучайся сразу писать все проверки и не полагаться на компилятор.
Имхо, правильно говорит T800. Если в бизнес приложении возникнет исключение переполнения, не обрабатываемое тобой, это ничем не лучше бага с переполнением в сях. И там и там прога не будет работать корректно. Поэтому сразу надо брать типы по размеру, а если есть опасение переполнения, предохранятьсяна свое усмотрение, но никак не средствами компилятора.
T800 Поясни почему? Наверняка это будет одной из причин зачем в С так сделано. perez Исключения на порядки лучше чем неверные результаты. И дело не в том что я не знаю о какомто странном поведении. Как я уже приводил примеры об этом не догадываются профессионалы. Сколько таких ошибок сделано потому что человек верит что компилятор если что... Микрософт в великой мудрости своей в С# добавил ключевые слова для этого. В Jave что?... В такие моменты становится страшно. Код (Text): if (launchMissiles = true) { FireNukes(); } Все правильно. Ошибок нет! Компилируется и работает!
МПМ и что? это ошибка программиста, а не компилятора. и потом, компилятор варнинг выплюнет (хотя, не каждый). ps: да, таким вот банальным образом и стираются с лица земли человечачьи цывилизатцыи, да. self-destruction рулит 8-)
МПМ Целочисленная арифметика является слишком performance-critical чтобы на каждое действие вешать assert. Нужно заранее подумать о том, какие числовые данные в каких диапазонах будут выступать. Если у тебя работает и дворник Вася Петров и губернатор Рома Абрамович, то введи отдельный класс money, который будет реализовывать семантику целочисленной арифметики с проверкой переполнения.
МПМ Это пример запредельного нубства. Следуя твоей логике это тоже компилируется и работает: Код (Text): int16 zarpalata, premia, naruki; premia = 16000; zarpalata = 16000; naruki = premia - zarpalata; Однако это не питфол языка, а его элементарное незнание.
Логично, полностью согласен. Почему тогда в язык не ввести дополнительно ключевое слово? Скажем там где надо проверять пусть у нас будет checked int\float и тп... тоже самое для массива\указателя. Однако в С \ С++ такого нет. Те имхо былобы логично переложить часть работы на компилятор вместо кучи if-ов в коде. Разве нет?
МПМ Именно! Именно поэтому ты можешь написать свой класс и сделать нечто вроде: #if defined (_DEBUG) typedef integer my_checked_class; #else typedef integer int; #endif И определить свою собственную политику контроля результата, а не довольствоваться навязанной компилятором. Ну вот как ты себе это представляешь? Какое поведение будет наиболее логичным и покроет потребности всех пользователей?
МПМ "Ловить" эксепшены не очень здорово. Лучше везде (где нужно) расставить if. Передавать ошибки так же, как они передаются в WinAPI (например). И разобраться в этом легче, да и работать будет быстрее. ТС соовертую хоть чуточку покодить на асме, что бы пропало желание "переложить часть работы на компилятор" !!!
МПМ именно из-за этих инструкций salc(setalc), setxx cmovxx. их аналоги редко встречаются в микроконтроллерах, да и не во всех x86 они есть. к примеру у меня есть 2 компьютера, которые не поддерживают эти инструкции. конечно если ориентировать программу только на современные компьютеры - то никаких проблем, а вот на контроллерах это сильно замедлит программу. да и я с Вами полностью согласен. зачем что-то создавать самому и в результате замедлять программу еще больше. почему компилятор не может встраивать код проверки при указании этой необходимости. в хорошем компиляторе должно быть реализовано все просто. в данном случае проще будет компилятору проверять переполнение, чем писать классы (что гораздо больше принесет неудобств программисту и, как тут уже говорилось, замедлит программу, тем более что эта возможность предоставляется процессором).
Кодить на асме учусь. Поэтому и стали возникать подобные вопросы. _DEN_ Полагаю добавление ключевого слова checked былобы безболезненно. Везде где его нет будет считаться что программист был умный и сам все проверил, добавление в объявление переменной заставит компилятор приделать проверку. Это минимальное изменение поведения. Однако оно не вскроет старых ошибок. Поэтому unchecked былобы правильнее с точки зрения безопасности. Все что им не помечено должно проверяться. Да будет тормозить. Да будет много сломаного кода. Но тут вопрос, что лучше, когда он молча имеет трещину или когда мы его надломили и увидели недостатки? Хотя мой первоначальный вопрос имел в виду "почему это небыло сделано в самой перовой K&R С"? Сейчас уже конечно никто не будет менять, устоялось, сложилось так. Опятьже хочу уточнить, почему я говорю про компилятор. 99% тех кто называется програмистами - тупы. Некоторые невероятно тупы. И они пишут для нас код по принципу "собралось = работает". Да возможно С был здауман не для миллионов идиотов. Но вышел С++ и теперь любой может выстрелить не только себе в ногу, но с появлением интернета и дешевых CPU в любую ногу на планете. Уточняю точку зрения ещё раз, С и С++ очень опасные языки, С++ опаснее на порядки. И благодаря неведомым гениям маркетинга он стал промышленным стандартом. На нем уже написаны гигабайты кода. Этот код будет продолжать работать ещё очень долгое время. И в компиляторах... печально. Я сомневаюсь что когданибудь буду знать хотябы про С всё те всегда буду вынужден полагаться на комплиятор. А некоторые не будут и пытаться узнать. Компиль - ран. Бабло. Ура. Мысль в том что окромя хакеров, в этом мире программируют и несколько миллионов дрессированных обезъян. Я не доверяю даже себе. Я постоянно проверяю что я делаю. Но я должен доверять что ктото поставит эти проверки в своём коде? О.о Да. Я согласен глупо напрягать компилятор. Мыто с вами знаем в чем дело. Теперь про профессиАналов. Улыбнемся. Но самая крупная катастрофа ракеты типа "Ариан-5" произошла 4 июня 1996 года. Пятидесятиметровая громадина стартовала, неся четыре спутника на борту. Через 30 секунд полета ракета отклонилась от траектории, и, чтобы предотвратить ее падение на город Кура, пришлось взорвать ее командой с Земли. Обломки "Ариан-5" были разбросаны в радиусе 17 км. Катастрофа нанесла не только денежный ущерб - сотни миллионов долларов, но и не поддающийся подсчетам экологический ущерб: люди, проживающие в радиусе 25 км от места взрыва, в течение трех суток жаловались на жжение в глазах и проблемы со слухом. Как показало расследование, причиной аварии "Ариан-5" стала ошибка программиста. В одной из подпрограмм системы инерциальной ориентации содержался некорректный код преобразования 64-битного числа с плавающей точкой в 16-битное целое со знаком. Ошибка программиста уничтожила аппарат стоимостью в сотни миллионов долларов! Воистину, этот случай по праву достоин того, чтобы быть занесенным в Книгу рекордов Гиннеса. Однако, строго говоря, несправедливо винить во всем лишь одного программиста, писавшего код. Ответственность в полной мере несут и разработчики компилятора, из-за которого стала возможной подобная ошибка. Ибо по-настоящему надежный компилятор должен отлавливать такие баги еще на стадии трансляции. Конечно не про переполнение целого. Но очень рядом. Что хорошо - у девайса выскочил ексепшн. А ведь он мог не в воздухе рвануть, а упасть кудато и бухнуть на земле... Знаете как был взлома первый раз xbox360? В гипервизоре int64 перед проверкой приводился в int32. Это позволило ушлым ребятам в верхней части спрятать что надо и выполнить нужный код... Оно конечно круто что язык считает программиста умнее. Это приятно. Но в большинстве случаев - зря. Так вот возвращаясь к вопросу. Все ответившие(почти ) считают что это проблема программиста помнить, чтото, гдето, можетбыть пойдет не так. При определенных обстоятельствах. И именно поэтому в компиляторе, в книге K&R и всех новых, нигде не упоминается что переполнение вообще может возникнуть? Это заговор!!!
Спасибо за ответ. А можно немножко по подробнее, для человека который знаком с ассемблером по минимуму, только почитать отладчик своей программы. Особенно про момент что "не во всех х86 они есть", я думал нам нужны только флаги переполнения и переноса? Понимаю что наверное утомил уже своими портянками но всётаки прифлужу ещё кусочек. По сути мы имеем дело с "дырявой абстракцией". Те язык очень красиво отошел от кучи ассемблерных инструкций и разницы в CPU, при этом оставив за бортом что для компьютера а=б+с это весьма не так просто как мы видим. Те скрыл сложность, не предупредив об опасности. Закрыл плащиком яму с шипами. В тот день батарея ракет «Пэтриот», развёрнутая для защиты американских военных объектов в Дхаране, не смогла перехватить иракскую ракету «Скад», причиной чего было официально названо неадекватное вычисление в формате с плавающей точкой. Система управления ракеты «Пэтриот» имеет внутренние системные часы, отсчитывающие время в десятых долях секунды. Для перевода подобного времени в формат с целыми секундами компьютер просто умножает данные на 1/10. С идеальной математической точки зрения эта операция безошибочна, но в вычислительном отношении на современных цифровых ЭВМ с двоичной системой счисления она катастрофична. Дело в том, что дробь 1/10 не имеет точного внутреннего представления на подобных компьютерах и должна быть приближена подходящей двоичной дробью. В качестве такого приближения американские разработчики взяли 24-битное двоичное число 0.00011001100110011001100, которое меньше, чем 1/10, примерно на одну миллионную. Это кажется исчезающее малым, но ведь подобная погрешность постепенно накапливается! После четырёх дней непрерывной работы расхождение системного времени с точным достигло трети секунды, что в комбинации с другими особенностями программного обеспечения «Пэтриотов» привело к ошибке наведения в 700 метров. В результате ракета, выпущенная на перехват «Скада», угодила в барак с американскими военнослужащими, убив 28 человек. yah baby! if - then. А ведь знали, моглибы вставить код проверки накопленой погрешности. Ну тут я конечно немного агресивен и диалог веду некоректно. Прошу прощения. Но не могу не привести пример, людям нельзя доверять. Если приходится это делать то лучше доверять меньшему количеству людей.