Часто данные в файле записываются в виде: сколько-то байтов - размер очередной порции, затем - собственно данные. Изучал код на Си, читающий такой файл, и наткнулся на следующую конструкцию:
Выяснилось, что после такого чтения size легко может оказаться отрицательным, даже если и был записан вполне себе положительным (например, если первый байт был равен 0x1, а второй - 0xff).
Итак, возьмем пример с такими первыми двумя байтами: 0x01 0xff.
Здесь есть два интересных для меня момента:
Избежать этого можно двумя путями.
char sizeLow, sizeHigh; int size; FILE * pFile; pFile = fopen ("myfile.bin" , "rb"); fread (&sizeHigh, 1, 1, pFile); fread (&sizeLow, 1, 1, pFile); size = (sizeHigh << 8) | sizeLow;
Итак, возьмем пример с такими первыми двумя байтами: 0x01 0xff.
Здесь есть два интересных для меня момента:
- При чтении оба эти байта записываются в переменные типа char. Знаковость или беззнаковость char зависит от реализации компилятора. В моем случае (gcc version 4.8.2) char оказался знаковым. То есть младший байт хоть и имеет 16-ричное представление 0xff, но хранится и воспринимается компилятором скорее как -1.
- Для побитовых операций наименьший размер операндов - int. То есть в нашем случае все операнды сначала неявно приводятся к int, а затем только вычисляется выражение.
Избежать этого можно двумя путями.
Первый: обнулить старшие байты операндов после расширения до int:
Теперь, даже если char был знаковым, и при расширении до int знаковый бит расширился на все старшие биты, они тут же обнуляются с помощью побитового "И" с 0xff.
Второй вариант - это использовать беззнаковые типы:
В этом случае sizeLow при расширении до int сохраняет значение 0xff, поэтому в итоге в size мы получим нужное нам значение 0x01ff.
Следует отметить, что в случае C++ упомянутый gcc 4.8.2 ведет себя точно так же.
size = (sizeHigh & 0xff << 8) | (sizeLow & 0xff);
unsigned char sizeLow, sizeHigh; int size; // здесь тоже логично использовать беззнаковый тип, но это не существенно в данном примере FILE * pFile; pFile = fopen ("myfile.bin" , "rb"); fread (&sizeHigh, 1, 1, pFile); fread (&sizeLow, 1, 1, pFile); size = (sizeHigh << 8) | sizeLow;
Следует отметить, что в случае C++ упомянутый gcc 4.8.2 ведет себя точно так же.
Лучше конечно пользоваться stdint с uint8_t а для размеров только size_t
ОтветитьУдалитьда, лучше. Но сейчас посмотрел внимательнее на проблему: изначально использовался std::ifstream и, соответственно, метод read (char* s, streamsize n), который хочет получать указатель на char. И единственный вариант тут, похоже, это сделать после расширения &0xff, и обнулить старшие биты. Теперь у меня возник вопрос, зачем был взят тип char для буфера в read.
УдалитьУ GCC есть два управления: -fsigned-char и -funsigned-char . Это на случай, когда окажется под рукой компилятор не той системы.
ОтветитьУдалить$ gcc -v --help | grep "signed-char"
-fsigned-char Make "char" signed by default
-funsigned-char Make "char" unsigned by default
www.wien.pp.ua
ОтветитьУдалить