В предыдущем видео мы с вами изучили, как работать со стандартными потоками ввода-вывода при выполнении бинарной сериализации и десериализации. Однако у них есть важная особенность, которую надо изучить, чтобы у вас не возникало проблем. Особенность эта заключается в том, что программа ваша может достаточно долго корректно работать, но в какой-то момент взорваться. И чтобы этого не случилось, давайте разберемся, о чем речь. А дело в том, что при работе как с файловыми потоками, так и с потоками ввода-вывода из строки, их нужно открывать в специальном бинарном режиме, если мы хотим выполнять именно бинарную сериализацию и десериализацию. Вот у меня на экране представлены конструкторы файловых потоков и потоков ostringstream и istringstream, в которые передается специальный дополнительный параметр std::ios_base::binary, и этот параметр, собственно, открывает эти потоки в специальном бинарном режиме. И давайте же мы разберемся, чем бинарный режим потоков ввода-вывода отличается от текстового. Когда мы сериализуем данные, в частности, сохраняем их на диск, то весьма вероятна ситуация, когда мы сохраняем, например, какой-то документ на флешку на компьютере, который работает под управлением операционной системы Windows, а потом читаем этот документ под Linux. Такая ситуация вполне себе возможна, и вот мы сейчас эту ситуацию с вами сэмулируем. У меня есть две программы, вот они у меня в текстовом редакторе открыты, это файл writer.cpp и файл reader.cpp, writer.cpp устроен очень просто, он объявляет целочисленную переменную value, которая равна 305 400 406, и бинарно сохраняет эту переменную в файл t.bin, который у нас открыт в файловом потоке ofstream, и есть файл reader.cpp, который открывает этот самый файл t.bin и читает из него целое число типа uint32_t. Ну и, собственно, обе эти программы, writer.cpp выводит в консоль то значение, которое он записал, а reader.cpp выводит то значение, которое он считал. Оба эти файла у меня лежат в одной папке, и дальше я делаю вот что. Я открываю самую обычную консоль Windows. Давайте посмотрим, что у меня здесь есть в папке, у меня есть файл reader.cpp и writer.cpp. Точно так же рядом я открываю консоль WLS, Windows Linux Subsystem — это маленький Linux, который появился внутри Windows 10. И у меня здесь открыта та же самая папка, вы можете видеть, что в ней есть файлы writer.cpp и reader.cpp. Дальше мы делаем вот что. В консоли Windows, то есть в Windows-машине мы компилируем writer.cpp, witer.cpp и назовем наш файл, исполняемый файл, writer. Более того, когда он скомпилируется, мы его сразу же запустим. Вот мы writer.cpp скомпилировали, запустили, и он вывел 305 400 406, ровно то число, которое у нас есть в программе. Дальше мы идем в Linux и делаем здесь то же самое, мы компилируем, теперь только не writer.cpp, а reader.cpp, -std=c++1z, ./reader.cpp -oreader, и точно так же, после того как скомпилируется, мы программу нашу запускаем. Компилируем, запускаем. У нас все скомпилировалось, программа отработала, но она вывела другое число. То есть мы видим, что мы записали в файл 305 миллионов и так далее, а считали мы 873 074 006. То есть что-то у нас идет не так. Хорошо, идем в наши файлы writer.cpp и reader.cpp и открываем наши файловые потоки в бинарном режиме. Добавляем параметр binary в оба файла и повторяем наши манипуляции с компиляцией и запуском программы под Windows и Linux. Компилируем и запускаем writer, все, он отлично отработал, снова вывел 305 400 406. и делаем то же самое для reader. Оп! Супер, наша программа скомпилировалась, отработала и вывела то число, которое мы ожидаем. То есть добавление бинарного режима в файловые потоки ofstream и ifstream починило нашу программу, так что бинарный режим потоков действительно играет какую-то роль. И давайте мы с вами разберемся, что же происходит. Для этого мы сделаем вот что. Мы перейдем в наши файлы writer и reader и отключим на время бинарный режим, а число, то, которое мы записали, мы будем выводить в шестнадцатеричном виде. Ну и то же самое у нас будет происходить в reader, тоже убираем бинарный режим и точно так же считанное число выводим в шестнадцатеричном виде. Так, открываем консоль, компилируем, запускаем, и вот мы получили шестнадцатеричное представление нашего числа, и теперь давайте сделаем то же самое в Linux. Ага, мы видим, что, как у нас и было, наша программа выводит не то число, которое мы туда записали. Давайте смотреть, собственно, что происходит. Видите, у нас последний байт числа — 56 и там, и там, дальше у нас идет байт 0a, а здесь откуда-то появился странный байт 0d. И уже после него идет 0a, 34, и вот здесь тоже 0a, 34. Очень странное дело. На самом деле объяснение этой странности есть на cppreference в статье про ввод-вывод. Здесь есть раздел, который объясняет разницу между бинарным и текстовым режимами файловых потоков. Разница эта заключается вот в чем. Как, я думаю, многие из вас знают, в операционной системе Windows для обозначения перевода строки используется пара символов \r\n, или же в байтах это 0D0A, или, в десятичной системе, 1310. При этом когда мы с вами программируем на C++, неважно, под какую платформу мы это делаем, мы всегда в коде для перевода строки используем только один символ \n. Так вот, файловые потоки, даже не файловые потоки, а потоки ввода-вывода в стандартной библиотеке, открытые в текстовом режиме под операционной системой Windows, встречая вывод перевода строки \n, автоматически добавляют в него, добавляют к нему еще один символ \r. Теперь переходим в Linux. В linux для перевода строки используется только один символ \n, или 0A, 10. Соответственно, в Linux нет разницы между текстовым и бинарным режимами, потому что в текстовом режиме нет необходимости отслеживать, чтобы рядом с символом \n был символ \r. Что же происходило в нашей эмуляции Linux и Windows? Мы под Windows сохраняли число, внутри которого содержится байт 0a, или \n, или символ перевода строки. И так как в файле writer.cpp файловый поток открыт в текстовом режиме, то рядом с этим байтом 0a дописывался байт 0d, 13. В Linux же считывались те байты, которые к нам пришли. И здесь, на самом деле, мы можем еще обратить внимание на интересный факт. Давайте мы посмотрим содержимое папки, и мы видим, что файл t.bin имеет размер пять байт. Пять, хотя мы сохраняем в него число типа uint32_t, то есть четыре байта. Вот как раз этот пятый дополнительный байт возникает оттого, что мы в текстовом режиме под операционной системой Windows добавляем байт 0d. Когда же мы передаем в конструктор потока ofstream параметр binary, мы отключаем эту магию, связанную с тем, чтобы добавлять дополнительный байт. И теперь есть очень интересный момент, который состоит в том, что если мы просто посмотрим на программу в файле writer.cpp, то по ней вообще ни разу не очевидно, что она имеет какое-то отношение к переводам строки. Таким образом, давайте с вами подведем итоги. Если вы выполняете бинарную сериализацию или десериализацию данных с помощью потоков ввода-вывода из стандартной библиотеки C++, то надо обязательно открывать их в режиме std::ios_base::binary, потому что, как мы сейчас с вами видели, ваша программа может, на первый взгляд, работать правильно, но в какой-то момент, когда в нее попадут бинарные данные, содержащие байт \n, байт 10, у вас могут возникнуть очень странные эффекты. Поэтому не забывайте открывать свои потоки в бинарном режиме.