С++ чтение беззнаковых значений int из байтового буфера

После более чем 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!

Что я сделал не так при первой попытке?

Предполагается, что эта программа повышает производительность, и я не думаю, что объявление двух дополнительных переменных (включая их сборку мусора) — самый быстрый способ сделать это.

Или есть другой, лучший способ?

Я предполагаю, что проблема с целочисленной подписью. 0xC6 при повышении до int становится 0xFFFFFFC6. Поэтому простое решение — использовать целые числа без знака. Вы используете нестандартные имена типов: стандартное 8-битное целое число — int8_t, а стандартное 8-битное целое число без знака — uint8_t. Аналогично для 16- и 32-битных целых чисел. Эти типы определены в шапке <cstdint>. Поэтому переключитесь на беззнаковые байты, и проблема должна исчезнуть.

john 07.08.2024 17:51

Привет, Джон, спасибо за твою идею, это имело бы смысл. Я попробовал и переписал свой код следующим образом (и оптимизировал параметр 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 07.08.2024 18:10

Что мне интересно: когда я делаю это шаг за шагом, с отдельными беззнаковыми байтовыми переменными, все работает нормально. Когда я пытаюсь сделать это в одной строке, без отдельных переменных без знака, этого не происходит. Возможно ли, что часть «конкретно без знака» каким-то образом теряется, когда я делаю это в одной строке? Если да, то как мне вернуть этот факт?

AlexisVanWien 07.08.2024 18:16

@AlexisVanWien Не видя вашего точного кода в виде минимального воспроизводимого примера , сказать невозможно. Предположительно, типы некоторых переменных все еще неверны.

john 07.08.2024 18:22

включая их сбор мусора. Сбора мусора нет. Они не в куче. Оптимизатор компилятора невероятно хорош. Пишите код для людей. Не пытайтесь писать код для микрооптимизаций, которые компилятор (вероятно) сделает за вас — но если вы это сделаете, убедитесь, что ваш тщательно настроенный вручную код действительно работает лучше, чтобы оправдать написание более сложного для понимания кода. Например, некоторые программисты повторно используют переменные, потому что «чем меньше ресурсов, тем лучше», но компилятор выполняет статическую оптимизацию однократного присваивания (SSA) и «отменяет» все эти неэффективные махинации.

Eljay 07.08.2024 18:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
61
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Тип 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 для хранения содержимого файла.

Remy Lebeau 07.08.2024 18:39

Джон, ты мягко направил меня в правильном направлении, а Реми, ты швырнул решение прямо мне в лицо. Обоим: СПАСИБО, вы (оглядываясь назад: очевидно) правы. Повторное приведение к (unsigned char) помогло. Я еще не знаю, что делает std::vector, но я исследую. Джон, если тебе все еще нужен минимальный воспроизводимый пример, он у меня уже готов (делал это, пока Реми писал свой ответ). Не стесняйтесь, дайте мне знать, если вы все еще этого хотите. Еще раз: спасибо вам обоим.

AlexisVanWien 07.08.2024 18:54

Все равно решил опубликовать для возможных будущих читателей: Моя первоначальная проблема (как я думаю) в минимально воспроизводимом примере) с решением Реми Лебо, реализованным в ней. Наслаждаться!

#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), работает отлично, в отличие от моего «все в одном предложении», где я понятия не имел, что компилятор делает с байтами, смешивая их. вместе... ну да ладно.

И да, я не знал, что удаление байта из строки — это, по сути, байт со знаком, тогда как мне нужно было, чтобы он был без знака.

Престижность Джону и Реми, и я надеюсь, что это поможет будущим читателям.

Всем здоровья! АлексисВанВен

Другие вопросы по теме