Разрабатывая функцию для работы с программой я проговариваю про себя или вслух алгоритм ее работы. Иногда комментирую вслух, при этом всегда стараюсь написать комментарий в программе. Но вот программа написана, я ее запускаю, и вижу, что происходит отключение программы с ошибкой.
Программа простейшая, и найти причину вылета не составило большого труда. Причину вылета программы, но вот что приводит к стечению подобных обстоятельств? Ведь по идее везде, где был хоть малейший намек на ошибку я поставил условия проверки.
Теперь объясню, что это за программа: для одного из проектов мне понадобилась очередь данных. Данные читаются из потока, и сохраняются в очередь. В одной записи может быть от 1 до 512-ти байт. Разумеется потом данные из очереди достаются и обрабатываются. Длина записи выбиралась случайно, а очередь состояла из случайных байт. Не ставилась задача обработки, только заполнение очереди. Главное, что надо было соблюсти - что хранилище было фиксированного размера, и не расширялось. Соответственно, если происходило переполнение, то программа вылетала с ошибкой (нельзя было допустить переполнения). Однако, если никто не забирал данные из очереди, надо было стирать более ранние записи, и дописывать новые. В идеале до конца очередь не должна была добраться, но судьба могла распорядиться иначе, и надо было проверить, что будет, если данные начнут поступать слишком часто.
Программа для проверки была написана на основе генератора случайных чисел, сначала я получал число байт, которые требовалось сохранить в очереди, а затем генерировал такое количество байт. Массивы для хранения я расположил так, чтобы они затирали друг друга в случае переполнения, чтобы сразу получить ошибку. А для получения длины я придумал следующий алгоритм: получаем один байт от 0 до 255, прибавляем к нему 1, получаем второй байт от 0 до 255 и прибавляем к первому. Итак мы имеем диапазон от 1 до 512-ти. Разумеется все переменные могут вместить этот диапазон. Да, забыл написать, что я работаю в основном в *NIX и решил не заморачиваться генерацией случайных чисел, а использовать файл
/dev/urandom, читаем из него один байт в unsigned short, и не паримся. При прибавлении 1 в unsigned short нам хватит места, чтобы сохранить нужное число.
После чего читаем из того же файла необходимое число байт.
Разумеется я не мог знать, портятся данные или нет, ибо я не знал заранее последовательностей байт, поэтому ввел контрольные суммы в блоки данных, чтобы проверять целостность. Ну вот и все, ах да, чтобы читался именно один байт я ввел отделение мледшего байта по маске, чтобы не выбиться за число 255.
Программа вылетает с ошибкой. Понимаю, что затираются данные. Такое может быть, если прочитать более 512-ти байт, но такое невозможно. Проверяю в отладчике, действительно, длины иногда доходят до ~700 байт. Значит где то неправильно генерируется длина! Но там всего три действия, плюс отделение по маске. Я потратил 3 часа, чтобы проверить все возможные варианты, сомнений не было, ошибка именно в длине, но как такое возможно? неужели компилятор начудил? или оптимизатор удалил что то важное? Нет, наверняка была ошибка у меня. Еще раз: читаем первый байт, накладываем маску, прибавляем единицу, читаем второй байт, накладываем маску, прибавляем первый байт... Все правильно. Не работает.
И тут мой взгляд упал на наложение маски. Видимо я опечатался, когда писал программу, и вместо операции &= написал операцию ^=, а это не наложение маски, а инверсия. Но я проговаривал про себя "наложение маски", и особо не смотрел на знак операции. Таким образом я долго не мог заметить ошибку.
Теперь о том, что происходило: если я читал случайный байт = 255, и прибавлял к нему 1, то у меня 1 переносилась в старший разряд, а младший обнулялся. Затем операцией A^= 0x00FF (инверсия младшей части) я увеличивал первое число до 511-ти(0x01FF), и затем прибавлял другое случайное число до 255-ти. В итоге там получалось число больше 512-ти, что вызывало чтение за пределы буфера и затирание части переменных. Удивительно, но отца русской демократии не спасло удаление маскирования, после вычислений выяснилось, что без маски ошибка переполнения возникала на шаг после пресловутой суммы 255+1.
Череда ошибок была вызвана процессом чтения из файла, т.к. функция read() рассматривает любой блок данных, как массив, а читала она один байт, то она не трогала другой байт, в котором оставалась единица от предыдущей операции.
Проблема решилась простым обнулением в начала цикла обеих переменных. Маску я так и не вернул на место, она была лишней.
Однако ошибка именно в этом алгоритме меня не сильно расстраивала, я написал его только для отладки функции, и он бы все равно дольше этой отладки не прожил. Однако благодаря поиску ошибки я теперь знаю, что иногда самый легкий путь требует много большей внимательности, чем сложный, ведь напиши я функцию A = rand(), ничего бы не случилось, тут старший байт нулится самостоятельно. Такая вот детская ошибка, стоившая трех часов отладки. А все из-за оговорки, что мы накладываем маску, а скажи я, что мы делаем операцию &(И) я бы сразу заметил, что маска накладывается не правильно.