Комментарии 42
В борландовском турбо-паскале, кстати, очень удобно было: хочешь включаешь выдачу сообщений об index out of bounds, хочешь — отключаешь. При отладке такую ошибку поймает, а в релизной версии будет работать без оверхеда.
ЗЫ перенесите в тематический блог
Статья интересная — спасибо — но остаётся не понятным, как вы нашли ошибку в вашем конкретном примере. Какими средствами? Следуя какой логике? Или это know how?
Если вы опытный программист C, вы увидите как я сделал чтобы появлялась единица, и почему она появляется в разных местах.
в данном случае силой мысли
Я не пишу на Си, но ошибку тоже сразу увидел.
Если такие игры с памятью встречаются в коде, того кто это писал «нужно долго бить головой об бэкспейс» (с) bash.org.ru
Если такие игры с памятью встречаются в коде, того кто это писал «нужно долго бить головой об бэкспейс» (с) bash.org.ru
в реальном коде подобная ошибка скорее всего не явной.
Тогда нужно делать проверки на «out of bounds». Раз уж крутости хватило на прямую работу с памятью, то на проверки диапозона и подавно должно хватить.
*диапазона
Что такое «прямая работа с памятью»? Данный пример демонстрирует как раз кривую =)
В языке C практически повсеместно идет работа с памятью, поэтому эта «крутость» вынужденна.
В таком случае в каждом учебнике языка «С» на первой же странице должна быть надпись огромными буквами:«Не забывай делать проверки диапазона или сгори в аду!»
дак это итак все знают, однако от таких ошибок не застрахован никто. если писать уж очень параноидально, то возможно это сократит кол-во ошибок до минимума, но замедлит работу программы. есть местах где это очень критично.
просто подобна ошибка может быть вызвана огромным кол-вом стандартных функций. да и дело не ограничивается только переполнением.
допустим у функция strcpy которая копирует набор байт из одной области памяти в другую, пока не наткнется на null-байт, но иногда бывает что выделенной памяти не хватает, допустим потомучто в конец исходной строки забыли поставить null-байт.
у неё есть «безопасный» аналог strncpy, за исключением того, что последним параметром принимает максимальное кол-во байт. рядо ошибок это конечно срезает, зато оставляет ещё челую кучу. например программист может ошибиться в максимальном количестве байт на единицу, это вызовет «однобайтовое переполнение», которое может не выявиться вообще никогда, а может и при первом запуске.
также вероятно раскрытие данных когда злоумышленник может передать исходную строку без null-байта намеренно и тогда если массив под строку назначения достаточно большой и она отображается у злоумышленника то он может увидеть некоторое кол-во данных из стека. иногда бывает пароль, иногда путь какой-нибудь. и это только вершина айсберга. ну вобщем вы поняли.
допустим у функция strcpy которая копирует набор байт из одной области памяти в другую, пока не наткнется на null-байт, но иногда бывает что выделенной памяти не хватает, допустим потомучто в конец исходной строки забыли поставить null-байт.
у неё есть «безопасный» аналог strncpy, за исключением того, что последним параметром принимает максимальное кол-во байт. рядо ошибок это конечно срезает, зато оставляет ещё челую кучу. например программист может ошибиться в максимальном количестве байт на единицу, это вызовет «однобайтовое переполнение», которое может не выявиться вообще никогда, а может и при первом запуске.
также вероятно раскрытие данных когда злоумышленник может передать исходную строку без null-байта намеренно и тогда если массив под строку назначения достаточно большой и она отображается у злоумышленника то он может увидеть некоторое кол-во данных из стека. иногда бывает пароль, иногда путь какой-нибудь. и это только вершина айсберга. ну вобщем вы поняли.
Согласен конечно, но лично я, в свои студенческие будни, сталкивался со случаем, когда был неправ компилятор. Например, код
a = c << i;
Будет работать по-разному в зависимости от значения i. Компилятор — Borland C++ 3.1. Причем если ввести данную строку (c << i) в отладчике, то значение в отладчике будет правильное.
a = c << i;
Будет работать по-разному в зависимости от значения i. Компилятор — Borland C++ 3.1. Причем если ввести данную строку (c << i) в отладчике, то значение в отладчике будет правильное.
Типы a, c, i целочисленные, правда не помню, знаковые ли.
а вы хотели, чтобы он одинаково работал не зависимо от i??
Я хотел бы, чтобы он правильно (т.е. ожидаемо) работал от значения i.
Причем, как я уже сказал, в пред просмотре (если в watch ввести то выражение) работает все правильно.
Причем, как я уже сказал, в пред просмотре (если в watch ввести то выражение) работает все правильно.
а можно конкретнее? что не так работало?
Если значение i >= 32 то вместо ожидаемого 0 я получал следующее:
a = c << (i % 32);
a = c << (i % 32);
Выдержка из стандарта языка С:
If the value of the right operand is negative or is
greater than or equal to the width of the promoted left operand, the behavior is undefined.
If the value of the right operand is negative or is
greater than or equal to the width of the promoted left operand, the behavior is undefined.
Хм, интересное замечание.
Получается нечего на компилятор пенять, коли рожа кривая ;).
К сожалению, в конкретном данном случае я все равно не понимаю, почему было именно такое поведение, при условии того, что в watch все отображалось правильно (конечно, я понимаю, что среда (где watch и был) — это не компилятор).
Но против стандарта не попрешь, хотя и не понятно зачем обнулять все остальное, кроме первых 5 бит…
Получается нечего на компилятор пенять, коли рожа кривая ;).
К сожалению, в конкретном данном случае я все равно не понимаю, почему было именно такое поведение, при условии того, что в watch все отображалось правильно (конечно, я понимаю, что среда (где watch и был) — это не компилятор).
Но против стандарта не попрешь, хотя и не понятно зачем обнулять все остальное, кроме первых 5 бит…
Среда отладки, в которой Вы смотрели watch, мог быть запросто собран другим компилятором. Или тем же компилятором, но с другими настройками.
А вообще стандарты выглядят странными до тех пор, пока не познакомишься с архитектурами, отличными от х86. Например, для процессора, который умеет сдвигать только на 1 разряд за раз, обнуление старших бит вполне логично выглядит.
А вообще стандарты выглядят странными до тех пор, пока не познакомишься с архитектурами, отличными от х86. Например, для процессора, который умеет сдвигать только на 1 разряд за раз, обнуление старших бит вполне логично выглядит.
Судя по всему дело не в оптимизации под процессор, а оптимизации самого процессора с последующим сохранением поведения последующими процессорами.
Вообще-то да, Вы правы:
shl, shr — Shift Left, Shift Right
These instructions shift the bits in their first operand's contents left and right, padding the resulting empty bit positions with zeros. The shifted operand can be shifted up to 31 places. The number of bits to shift is specified by the second operand, which can be either an 8-bit constant or the register CL. In either case, shifts counts of greater then 31 are performed modulo 32.
shl, shr — Shift Left, Shift Right
These instructions shift the bits in their first operand's contents left and right, padding the resulting empty bit positions with zeros. The shifted operand can be shifted up to 31 places. The number of bits to shift is specified by the second operand, which can be either an 8-bit constant or the register CL. In either case, shifts counts of greater then 31 are performed modulo 32.
А размерность «с» случайно не 32 бита? Почитайте про операцию сдвига, например в яве, сдвиг именно так и делается, на (i % 32)
Интересно, почему все-таки так делается.
Пока нашел здесь замечание:
Пока нашел здесь замечание:
Почему Java сокращает правый операнд оператора сдвига или грустная история о заснувшем процессореИнтересна история данного вопроса, ни кто не поделится более детальными ссылками?
Одной из главной причин введения сокращения было то, что процессоры сами сокращают подобным образом правый операнд оператора сдвига. Почему?
Несколько лет назад был создан мощнейший процессор с длинными регистрами и операциями ротации и сдвигам на любое количество битов. Именно потому, что регистры были длинными, корректное выполнение этих операций требовало несколько минут.
Основным применением данных процессоров был контроль систем реального времени. В данных системах самый быстрый ответ на внешнее событие должно занимать не более задержки на прерывание (interrupt latency). Отдельные инскрукции таких процессоров были неделимы. Поэтому выполнение длинных операций (сдвига на несколько бит и ротации) нарушало эффективную работу процессора.
Следующая версия процессора имплементировала эти операции уже по-другому: размер правого операнда сократился. Задержка на прерывание восстанавилась. И многие процессоры переняли данную практику.
Признаюсь честно — я любил валить на других. Но, то что я это хотя бы понял, уже маленькое достижение.
Собственно то что вы написали относится к любой диагностике — разделяй и властвуй.
НЛО прилетело и опубликовало эту надпись здесь
Я кстати когда переводил тоже очень долго думал. Название взято с википедии, если кто-то скажет более приятное нашему уху слово — поменяю в статье.
Скажите, а Вам известен стандартный способ проверки корректности работы с массивами и/или с локальными переменными в С? Чтобы описанный в топике баг отловился автоматически…
Практически любые статические анализаторы кода это выявят. Даже gcc:
a.c: In function ‘a’:
a.c:8: warning: array subscript is above array bounds
Если честно, на ошибку в компиляторе (Borland Builder) я нарывался! Я уже не помню, что это было — но смог разрешить её, перенеся пару переменных в static.
А откуда Вам известно, что это именно ошибка компилятора? Или код был тривиальный, как в рассмотренном примере?
борланд на то и называют багленд, что в его компиляторах неоднократно были ошибки
Блок работал сам по себе, но отказывал, будучи вставленным в программу.
Как-то раз (давно уже) мне случилось переносить математическую библиотеку из фортрана в си. Что характерно, я тоже пользовался Борланом, и симптомы были очень похожи на Ваши — тесты отдельных функций библиотеки проходили на ура, а при сборке библиотеки целиком наступал трындец.
Я тогда тоже на компилятор всё валил… А потом баги постепенно отловились :)
Я тогда тоже на компилятор всё валил… А потом баги постепенно отловились :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Нечего на зеркало пенять, коли рожа кривая