Russian Qt Forum
Апрель 30, 2024, 11:31 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: [РЕШЕНО] Почему образуются "лишние" байты при чтении из stringstream?  (Прочитано 6557 раз)
schmidt
Гость
« : Январь 08, 2017, 16:53 »

Добрый день,

Понадобилось реализовать блочное шифрование. Чтобы не заботиться о том, поступают ли данные из строки или файла, решил передавать в функцию шифрования потоки istream, ostream. Столкнулся с тем, что в конце расшифрованной строки образуется какой-то мусор. Чтобы проверить себя, создал отдельный проект, который только пишет и читает текст в/из stringstream и обнаружил ту же проблему.

Код
C++ (Qt)
#include <iostream>
#include <sstream>
 
#include <cstring>
 
using namespace std;
 
int main(int argc, char *argv[])
{
/* Исходная строка */
stringstream ss_in("Hello, World!");
clog << "ss_in.str().length() = "
<< ss_in.str().length()
<< endl;
 
stringstream ss_intermediate;
char buf[8];
size_t bytes_written_into_ss_intermediate = 0;
while (ss_in.good()) {
/* Читаем данные в буфер. т.к. шифрование
* работает с блоками по 64-бита */

memset(buf, 0, 8);
ss_in.read(buf, 8);
/* Здесь могла бы быть процедура шифрования... */
ss_intermediate.write(buf, 8);
bytes_written_into_ss_intermediate += 8;
}
clog << bytes_written_into_ss_intermediate
<< " bytes written into ss_intermediate"
<< endl;
 
clog << "ss_intermediate.str().length() = "
<< ss_intermediate.str().length()
<< endl
<< "'" << ss_intermediate.str() << "'"
<< endl;
 
stringstream ss_out;
size_t bytes_written_into_ss_out = 0;
/* А здесь по неясной мне причине в потоке
* оказывается 24 байта доступных для чтения,
* 16 из которых были в него записаны,
* а еще 8 NULL-байтов появились каким-то
* непостижимым образом. Может это проделки
* запасливого распределителя памяти, заранее
* хапнувшего себе еще 8 байт capacity под
* будущие нужды, но с какой радости он
* пихает их в поток как данные? */

while (ss_intermediate.good()) {
memset(buf, 0, 8);
ss_intermediate.read(buf, 8);
ss_out.write(buf, 8);
bytes_written_into_ss_out += 8;
}
clog << bytes_written_into_ss_out
<< " bytes written into ss_out"
<< endl;
 
clog << "ss_out.str().length() = "
<< ss_out.str().length()
<< endl
<< "'" << ss_out.str() << "'"
<< endl;
 
getc(stdin);
}
 
 

Вывод:
Цитировать
ss_in.str().length() = 13
16 bytes written into ss_intermediate
ss_intermediate.str().length() = 16
'Hello, World!   '
24 bytes written into ss_out
ss_out.str().length() = 24
'Hello, World!           '

Непонятно, откуда берутся лишние 8 байт? Непонимающий
« Последнее редактирование: Январь 09, 2017, 11:46 от Schmidt » Записан
Swa
Самовар
**
Offline Offline

Сообщений: 170


Просмотр профиля
« Ответ #1 : Январь 08, 2017, 20:57 »

Может быть поэтому:
Цитировать
std::istream::read
istream& read (char* s, streamsize n);
If the input sequence runs out of characters to extract (i.e., the end-of-file is reached) before n characters have been successfully read, the array pointed to by s contains all the characters read until that point, and both the eofbit and failbit flags are set for the stream.
Записан
schmidt
Гость
« Ответ #2 : Январь 09, 2017, 06:19 »

Может быть поэтому:
Цитировать
std::istream::read
istream& read (char* s, streamsize n);
If the input sequence runs out of characters to extract (i.e., the end-of-file is reached) before n characters have been successfully read, the array pointed to by s contains all the characters read until that point, and both the eofbit and failbit flags are set for the stream.

Здесь говорится о том, что если символов в потоке меньше, чем запрошенное n, то в массив, на который указывает s, запишутся только те байты, что есть в потоке, а для самого объекта istream будут установлены флаги eofbit и failbit. Меня же интересует природа нулевых байтов, которые появляются в хвосте потока, словно были туда записаны.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #3 : Январь 09, 2017, 06:57 »

Так а что вам не понятно? Улыбающийся
У вас в потоке 16 байт. Вы вычитали 8 байт - это good, вы вычитали еще 8 байт - это тоже good, в потоке они были. Дальше вы читаете еще 8 байт, а это уже не good, их в потоке нет.
Все работает ровно так как вы написали.
« Последнее редактирование: Январь 09, 2017, 07:21 от Old » Записан
schmidt
Гость
« Ответ #4 : Январь 09, 2017, 08:27 »

Ох, во всем хочется видеть интуитивно понятные вещи. Но иногда все же приходится отбросить все предположения и рыться в документации до последней запятой.
Меня возмущает тот факт, что условие in.good() не останавливает меня до того, как я попытаюсь прочитать "пустой" поток. Как оказалось, в действительности дело обстоит так:

1. Я пишу в поток N байтов;
2. Я читаю из потока ровно N байтов;
3. Однако, чтобы isream понял, что байты закончились, его нужно пнуть еще на 1 байт вперед. чтобы он наткнулся на EOF. И только после пинка он установит eofbit.

И методу good() на полном серьезе следовало бы называться testBitGood() в соответствии с его назначением, а вовсе не размытым "все_хорошо"  В замешательстве
Таким образом условием while нужно либо заглядывать (peek) в следующий байт, чтобы он не был EOF, либо смотреть число реально прочитанных байтов, вызывая gcount() и выходить из цикла если прочитано 0.

Вот и пример такого интуитивно непонятного, но рабочего чтения из потока Улыбающийся
Код
C++ (Qt)
void testWriteAndReadStringstream( ) {
   const size_t BufferSize = 8;
 
   char buf[BufferSize];
   string str("Hello, World!");
   strncpy_s(buf, BufferSize-1, str.data(), BufferSize);
   stringstream ss;
   ss.write(buf, BufferSize);
 
   while (EOF != ss.peek()) {
       ss.read(buf, BufferSize);
       clog << ss.gcount() << " bytes read" << endl;
   }
}
 
« Последнее редактирование: Январь 09, 2017, 09:05 от Schmidt » Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #5 : Январь 09, 2017, 09:19 »

Наверно проще проверять флаг состояния сразу после чтения и сразу завершать цикл если чтение не удалось.
Записан
schmidt
Гость
« Ответ #6 : Январь 09, 2017, 09:52 »

Тогда мы рискуем "потерять" последние байты, которые не образуют целый блок Улыбающийся

Цитировать
http://www.cplusplus.com/reference/istream/istream/read/

If the input sequence runs out of characters to extract (i.e., the end-of-file is reached) before n characters have been successfully read, the array pointed to by s contains all the characters read until that point, and both the eofbit and failbit flags are set for the stream.

Например, мы читаем строку исходного текста перед шифрованием, длина которой может и не быть кратной размеру блока. Тогда в "хвосте" окажется несколько байт, пусть 2, а read мы просим постоянно читать 8. И в один прекрасный (последний) момент read читая следующие 8 байт прочитает 2 и натолкнется на EOF, после чего установит eofbit и failbit, но байты из "хвоста" все равно будут прочитаны.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #7 : Январь 09, 2017, 09:58 »

Тогда мы рискуем "потерять" последние байты, которые не образуют целый блок Улыбающийся
Если блоки могут быть не полные, то не очень правильно читать блоками, без проверки "А сколько там осталось?"
Всегда можно определить размер остатка и вычитать именно его.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #8 : Январь 09, 2017, 10:33 »

Таким образом условием while нужно либо заглядывать (peek) в следующий байт, чтобы он не был EOF, либо смотреть число реально прочитанных байтов, вызывая gcount() и выходить из цикла если прочитано 0.
Насколько помню, так всегда и было (не очень удобно как и многое в std). Обычно проверяют good() сразу после read(). При форматированном I/O этой проблемы нет, можно просто while (!ss.eof()).

Тогда мы рискуем "потерять" последние байты, которые не образуют целый блок Улыбающийся
А что это за байты такие? Просто и хорошо испускать exception если хоть что-то "не гуд", т.е. приложение не должно нарываться на конец файла. Для чтения данных переменной длины нужно предвычислить их число/размер используя длину файла/тега. А просто "прочитать все что есть в файле" может загнать приложение "куда Макар телят не гонял" 
Записан
schmidt
Гость
« Ответ #9 : Январь 09, 2017, 11:45 »

Так я нарочно читаю блоками, чтобы процедуру шифрования кормить 64-битными блоками Улыбающийся Меня здесь интересует только чтобы были прочитаны все данные и при необходимости последние байты из "хвоста" были дополнены нулями до размера блока. Поэтому я заранее определяю буфер по размеру блока, который перед каждым чтением очищаю.

Цитировать
А что это за байты такие? Просто и хорошо испускать exception если хоть что-то "не гуд", т.е. приложение не должно нарываться на конец файла. Для чтения данных переменной длины нужно предвычислить их число/размер используя длину файла/тега. А просто "прочитать все что есть в файле" может загнать приложение "куда Макар телят не гонял"

А зачем конкретно этой функции пересчитывать длину данных, если ее задача - обработать все поступающие из потока данные? Улыбающийся Что ей делать с этой длиной? Разве что выбрасывать исключение "Демо-версия программы не позволяет обрабатывать файлы размером больше N Kb".

Я всё же придерживаюсь идеи оставлять функции максимально простыми, чтобы их назначение было ясным и понятным, поведение - предсказуемым, а сами функции было легко документировать. Лучше строить приложения из вложенных вызовов простейших функций, чем писать одну супер-функцию, скрывающую с десяток режимов работы на основе десятка параметров. Меня, например, в дрожь бросает от воспоминаний о каком-нибудь Windows API.
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.136 секунд. Запросов: 22.