После более чем 20-летнего перерыва я снова начал возвращаться к программированию и начал заново изучать C++ (язык, который я практически никогда не использовал, раньше я занимался Object Pascal). Итак, я считаю себя полным новичком в C++.
Я пытаюсь написать программу, которая считывает весь файл в ByteStream
, а затем считывает из него значения. Я ожидаю, что (числовые) значения будут 2- или 4-байтовыми, беззнаковыми, с прямым порядком байтов (да, я усвоил основы).
Вот мои (глобальные) переменные:
std::string File_Content = ""; // entire file contents in ByteArray
unsigned __int32 File_Pos = 0; // position in the File_Content, next Byte about to be read
Вот функция, выполняющая чтение (возвращает значение по ссылке, чтобы не было бессмысленного копирования, поскольку там тоже есть строки):
static __int8 Read2Bytes(unsigned __int32 iPos, unsigned __int8 iEndian, unsigned __int16 &iReturn)
{
if (iEndian == 0) // Little Endian
{
// Push "second" byte to High Byte
iReturn = File_Content[iPos] | (File_Content[iPos + 1] << 8);
} // end if (iEndian == 0)
else
{ // Big Endian, not important right now
} // end else (iEndian == 0)
File_Pos += 2; // move the Position-Marker along
return 0;
} // end Read2Bytes
Вот я вызываю функцию для чтения 2-байтового целого числа:
unsigned __int16 iTimeStamp = 0; // 2 bytes
Read2Bytes(File_Pos, 0, iTimeStamp); // Position in the FileStream, Endian, Return Value)
Теперь, согласно WinHex, байты, которые я ожидаю прочитать из ByteStream
, читаются как 0xC6 0x9D
(именно в этом порядке). Итак, после преобразования порядка байтов я ожидаю, что iTimeStamp
вернется как 0x9DC6
, верно?
Но на самом деле он возвращается как 0xFFC6
, и я хоть убей не могу понять почему.
У меня есть еще одна функция, которая должна читать 4 байта, с прямым порядком байтов, LowWord-HighWord, и там у меня та же проблема.
Пожалуйста, может кто-нибудь открыть мне глаза на то, почему мой HighBytes
где-то теряется в переводе?
Редактировать:
Я немного поэкспериментировал, чтобы отладить проблему, и попробовал это:
static __int8 Read2Bytes(unsigned __int32 iPos, unsigned __int8 iEndian, unsigned __int16 &iReturn)
{
unsigned __int8 bHiByte, bLoByte; // both bytes separately
if (iEndian == 0) // Little Endian
{
// Not-Working Version
//iReturn = File_Content[iPos] | (File_Content[iPos + 1] << 8);
// new attempt, byte-by-byte
bLoByte = File_Content[iPos]; // read "first" byte
bHiByte = File_Content[iPos + 1]; // read "2nd" byte
iReturn = (bHiByte << 8) | bLoByte; // endian them together
} // end if (iEndian == 0)
(Rest unchanged)
и вдруг это работает! Из байтов 0xC6
и 0x9D
я получаю 0x9DC6
!
Что я сделал не так при первой попытке?
Предполагается, что эта программа повышает производительность, и я не думаю, что объявление двух дополнительных переменных (включая их сборку мусора) — самый быстрый способ сделать это.
Или есть другой, лучший способ?
Привет, Джон, спасибо за твою идею, это имело бы смысл. Я попробовал и переписал свой код следующим образом (и оптимизировал параметр File_Pos, поскольку он глобальный): static uint8_t Read2Bytes(uint8_t iEndian, uint16_t &iReturn) { if (iEndian == 0) { iReturn = File_Content[File_Pos] | (File_Content[File_Pos + 1] << 8); } // end if (iEndian == 0) else бла-бла, к сожалению, это ничего не изменило, HighByte все равно теряется -> 0xFFC6.
Что мне интересно: когда я делаю это шаг за шагом, с отдельными беззнаковыми байтовыми переменными, все работает нормально. Когда я пытаюсь сделать это в одной строке, без отдельных переменных без знака, этого не происходит. Возможно ли, что часть «конкретно без знака» каким-то образом теряется, когда я делаю это в одной строке? Если да, то как мне вернуть этот факт?
@AlexisVanWien Не видя вашего точного кода в виде минимального воспроизводимого примера , сказать невозможно. Предположительно, типы некоторых переменных все еще неверны.
включая их сбор мусора. Сбора мусора нет. Они не в куче. Оптимизатор компилятора невероятно хорош. Пишите код для людей. Не пытайтесь писать код для микрооптимизаций, которые компилятор (вероятно) сделает за вас — но если вы это сделаете, убедитесь, что ваш тщательно настроенный вручную код действительно работает лучше, чтобы оправдать написание более сложного для понимания кода. Например, некоторые программисты повторно используют переменные, потому что «чем меньше ресурсов, тем лучше», но компилятор выполняет статическую оптимизацию однократного присваивания (SSA) и «отменяет» все эти неэффективные махинации.
Тип char
вашего компилятора подписан. char
— это то, что вы получаете от std::string
.
Никогда не следует сдвигать или возиться со значениями знакового типа, если вы не очень хорошо знаете, что делаете. Немедленно переведите на unsigned char
:
iReturn = (unsigned char)File_Content[iPos]
| ((unsigned char)File_Content[iPos + 1] << 8);
Рассмотрите возможность использования static_cast
вместо приведения в стиле C. Или лучше просто используйте std::vector<unsigned char>
вместо std::string
для хранения содержимого файла.
Джон, ты мягко направил меня в правильном направлении, а Реми, ты швырнул решение прямо мне в лицо. Обоим: СПАСИБО, вы (оглядываясь назад: очевидно) правы. Повторное приведение к (unsigned char) помогло. Я еще не знаю, что делает std::vector, но я исследую. Джон, если тебе все еще нужен минимальный воспроизводимый пример, он у меня уже готов (делал это, пока Реми писал свой ответ). Не стесняйтесь, дайте мне знать, если вы все еще этого хотите. Еще раз: спасибо вам обоим.
Все равно решил опубликовать для возможных будущих читателей: Моя первоначальная проблема (как я думаю) в минимально воспроизводимом примере) с решением Реми Лебо, реализованным в ней. Наслаждаться!
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
//==============================================================================
// Global Variables
//==============================================================================
std::string File_Content = "";
uint32_t File_Pos = 0;
//==============================================================================
// read the entire file into a string/ByteArray
//==============================================================================
static uint8_t GetFileContents(const std::string sFileName)
{
uint32_t iFileSize = 0;
iFileSize = (uint32_t)std::filesystem::file_size(sFileName);
std::ifstream strStream(sFileName, std::ios::in | std::ios::binary);
if (strStream)
{
File_Content.resize(iFileSize);
strStream.read(File_Content.data(), iFileSize);
strStream.close();
} // end if (sStream)
return 0;
} // end get_file_contents
//==============================================================================
// read 2-bytes-int out of the string, considering Endian
//==============================================================================
static uint8_t Read2Bytes(uint8_t iEndian, uint16_t &iReturn)
{
uint8_t bHiByte, bLoByte;
if (iEndian == 0) // little Endian
{
// my first attempt, not-working, HiByte gets lost
//iReturn = File_Content[File_Pos] | (File_Content[File_Pos + 1] << 8);
// my 2nd attempt, WORKING, but too complicated
//bLoByte = File_Content[File_Pos];
//bHiByte = File_Content[File_Pos + 1];
//iReturn = (bHiByte << 8) | bLoByte;
// Remys solution, with re-casting to unsigned char per byte
iReturn = (unsigned char)File_Content[File_Pos] | ((unsigned char)File_Content[File_Pos + 1] << 8);
} // end if (iEndian == 0)
else
{ // Big Endian
// not importand here
} // end else (iEndian == 0)
// 2 bytes read, move along PosMarker accordingly
File_Pos += 2;
return 0;
} // end Read2Bytes
//==============================================================================
// read 4-bytes-int out of the string, considering Endian
//==============================================================================
static uint8_t Read4Bytes(uint8_t iEndian, uint32_t &iReturn)
{
uint8_t bByte1, bByte2, bByte3, bByte4;
if (iEndian == 0) // Little Endian
{
// my first attempt, not-working, HiByte gets lost
//iReturn = File_Content[File_Pos] | File_Content[File_Pos + 1] << 8 | File_Content[File_Pos + 2] << 16 | File_Content[File_Pos + 3] << 24;
// my 2nd attempt, WORKING, but too complicated
//bByte1 = File_Content[File_Pos];
//bByte2 = File_Content[File_Pos + 1];
//bByte3 = File_Content[File_Pos + 2];
//bByte4 = File_Content[File_Pos + 3];
//iReturn = bByte1 | (bByte2 << 8) | (bByte3 << 16) | (bByte4 << 24);
// Remys solution, with re-casting to unsigned char per byte
iReturn = (unsigned char)File_Content[File_Pos] | ((unsigned char)File_Content[File_Pos + 1] << 8) | ((unsigned char)File_Content[File_Pos + 2] << 16) | ((unsigned char)File_Content[File_Pos + 3] << 24);
}
else
{ // Big Endian
// not important here
}
// 4 bytes read, move along PosMarker accordingly
File_Pos += 4;
return 0;
} // end Read4Bytes
static uint8_t AnalyzeFileContents()
{
uint32_t Some4ByteInt = 0; // 4 bytes, unsigned
uint16_t Some2ByteInt = 0; // 2 bytes, unsigned
Read2Bytes(0, Some2ByteInt); // read 2 byte int from the string
Read4Bytes(0, Some4ByteInt); // read 4 byte int from the string
// ... and so on
// omitted: whatever is done with the values afterwards, not essential to the problem
return 0;
} // end AnalyzeFileContents
int main(int, char const* argv[])
{
std::string sFileName;
sFileName = "SomeFile.bin";
// read the entire file into the string
GetFileContents(sFileName);
// analyze the data in the string
AnalyzeFileContents();
} // end int main()
Оглядываясь назад, это довольно очевидно.
Моя попытка, когда я обрабатываю побайтово в отдельных переменных uint8_t (т.е. в частности, беззнаковое int), работает отлично, в отличие от моего «все в одном предложении», где я понятия не имел, что компилятор делает с байтами, смешивая их. вместе... ну да ладно.
И да, я не знал, что удаление байта из строки — это, по сути, байт со знаком, тогда как мне нужно было, чтобы он был без знака.
Престижность Джону и Реми, и я надеюсь, что это поможет будущим читателям.
Всем здоровья! АлексисВанВен
Я предполагаю, что проблема с целочисленной подписью. 0xC6 при повышении до
int
становится 0xFFFFFFC6. Поэтому простое решение — использовать целые числа без знака. Вы используете нестандартные имена типов: стандартное 8-битное целое число —int8_t
, а стандартное 8-битное целое число без знака —uint8_t
. Аналогично для 16- и 32-битных целых чисел. Эти типы определены в шапке<cstdint>
. Поэтому переключитесь на беззнаковые байты, и проблема должна исчезнуть.