Russian Qt Forum

Программирование => С/C++ => Тема начата: schmidt от Январь 08, 2017, 16:53



Название: [РЕШЕНО] Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: 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 байт? ???


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: Swa от Январь 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.


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: schmidt от Январь 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. Меня же интересует природа нулевых байтов, которые появляются в хвосте потока, словно были туда записаны.


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: Old от Январь 09, 2017, 06:57
Так а что вам не понятно? :)
У вас в потоке 16 байт. Вы вычитали 8 байт - это good, вы вычитали еще 8 байт - это тоже good, в потоке они были. Дальше вы читаете еще 8 байт, а это уже не good, их в потоке нет.
Все работает ровно так как вы написали.


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: schmidt от Январь 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;
   }
}
 


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: Old от Январь 09, 2017, 09:19
Наверно проще проверять флаг состояния сразу после чтения и сразу завершать цикл если чтение не удалось.


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: schmidt от Январь 09, 2017, 09:52
Тогда мы рискуем "потерять" последние байты, которые не образуют целый блок :)

Цитировать
http://www.cplusplus.com/reference/istream/istream/read/ (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, но байты из "хвоста" все равно будут прочитаны.


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: Old от Январь 09, 2017, 09:58
Тогда мы рискуем "потерять" последние байты, которые не образуют целый блок :)
Если блоки могут быть не полные, то не очень правильно читать блоками, без проверки "А сколько там осталось?"
Всегда можно определить размер остатка и вычитать именно его.


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

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


Название: Re: Почему образуются "лишние" байты при чтении из stringstream?
Отправлено: schmidt от Январь 09, 2017, 11:45
Так я нарочно читаю блоками, чтобы процедуру шифрования кормить 64-битными блоками :) Меня здесь интересует только чтобы были прочитаны все данные и при необходимости последние байты из "хвоста" были дополнены нулями до размера блока. Поэтому я заранее определяю буфер по размеру блока, который перед каждым чтением очищаю.

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

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

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