Почему read() принимает количество байт с помощью знакового типа?

90
26 июня 2022, 08:40

std::ifstream::read(char_type*, std::streamsize) принимает количество читаемых байт с помощью знакового типа std::streamsize, а std::fread(void*, std::size_t, std::size_t, std::FILE*) -- с помощью беззнакового типа std::size_t. При этом во многих примерах и там и там для количества байт используют sizeof, который выдаёт результат в std::size_t. Размер std::vector<char> тоже беззнаковый. Так почему тогда эта функция принимает знаковый тип? Как правильно кастовать типы, чтобы без проблем заполнить вектор байтов?

Answer 1

Думаю, правильный ответ - так сложилось исторически. Вначале в си/с++ использовали активно int для всего (в старом си это вообще был "тип по умолчанию"). То, что какие то файлы будут больше 2 Гигабайт даже никто не думал ("это громадные размеры") - сейчас так думают о 2 в 64. Плюс, оказалась очень удобно использовать отрицательные числа как признак ошибки. Тот же read/recv возвращает положительное число, если что то прочитали, ноль - если файл/сокет был закрыт и отрицательное, если ошибка. Удобно же.

Но потом математики посмотрели на это и говорят "ну как размер массива может быть отрицательным?" Плюс это приводит к ошибкам, когда пытаемся сложить два размера массива, а один из них отрицательный (потому что это на самом деле было кол-во прочитанных байт с ошибкой). И решено было, что оптимально будет хранить в беззнаковом типе, а ошибку возвращать другим способом (например, исключением или кодом возврата). Но не все пошли этим путем, в java беззнаковых типов нет (или уже появлялись?).

Но даже в таком случае многие не хотели отказываться от привычной -1 для ошибок. В плюсах куча мест, где используется 0xFFF...FF для индикации именно ошибки, что иногда вызывает прикольные баги (попытка выделить такое кол-во памяти обычно ничем хорошим не заканчивается).

Потом умные дядьки с комитета стандартизации ещё раз посмотрели и решили, хватит притворятся и давайте добавим ssize - он такой же как size, но только знаковый. Там есть интересные вещи, которые объясняют, зачем это всё нужно.

Answer 2

Тип std :: streamsize - это целочисленный тип со знаком, используемый для представления количества символов, переданных в операции ввода-вывода, или размера буфера ввода-вывода. Он используется как подписанный аналог std :: size_t, аналогично типу POSIX ssize_t.

За исключением конструкторов std :: strstreambuf, отрицательные значения std :: streamsize никогда не используются

В проекте стандарта C ++ есть следующая сноска 296 в разделе 27.5.2 Типы, в которой говорится:

streamsize используется в большинстве мест, где ISO C будет использовать size_t. В большинстве случаев streamsize может использовать size_t, за исключением конструкторов strstreambuf, которые требуют отрицательных значений. Вероятно, это должен быть знаковый тип, соответствующий size_t (это то, что Posix.2 называет ssize_t).

И мы видим, что в разделе D.7.1.1 конструкторы strstreambuf у нас есть следующие записи:

strstreambuf(char* gnext_arg, streamsize n, char *pbeg_arg = 0);
strstreambuf(signed char* gnext_arg, streamsize n,
   signed char *pbeg_arg = 0);
strstreambuf(unsigned char* gnext_arg, streamsize n,
   unsigned char *pbeg_arg = 0);

и говорится:

gnext_arg должен указывать на первый элемент объекта массива, количество элементов N которого определяется следующим образом:

И из следующего обсуждения мы можем увидеть, что n, имеющее тип streamsize, действительно требуется, чтобы иметь возможность принимать отрицательное значение:

  • Если n> 0, N равно n.

  • Если n == 0, N равно std :: strlen (gnext_arg).

  • Если n <0, N равно INT_MAX.

Answer 3

Как так вышло, что тип std::streamsize является знаковым целочисленным типом, судить не стану, однако, замечу, что даже если бы он был беззнаковым, то основную вашу проблему — "правильное преобразование типов" — это бы никак не решило.

Стандарт языка НЕ гарантирует, что

  1. Все значения типа std::size_t представимы типом std::streamsize,
  2. Все неотрицательные значения типа std::streamsize представимы типом std::size_t,
  3. Размер вектора std::vector<some_type> представим типом std::streamsize,
  4. Любое значение типа std::streamsize представимо типом std::vector<some_type>::difference_type
  5. Любое значение типа std::size_t представимо типом std::vector<some_type>::size_type.

Таким образом, для полной уверенности, что при преобразовании значения одного типа в значение другого типа не произойдёт неожиданного усечения, следует перед преобразованием проверить, поместится ли преобразуемое значение в целевой тип.

Например, явно:

if ( char_vect.size() <= static_cast<std::uintmax_t>(std::numeric_limits<std::streamsize>::max()) )
    fout.write(&char_vect[0], char_vect.size());

Или даже так:

static_assert( std::numeric_limits<std::vector<char>::difference_type>::max() <= std::numeric_limits<std::streamsize>::max() );

Максимальное количество элементов в векторе, между прочим, ограниченно сверху максимальным значением некоторого знакового целочисленного типа std::vector<some_type>::difference_type. Это обусловлено тем, что метод max_size() возвращает значение, равное значению distance(​begin(), end()) для некоторого максимально большого возможного вектора. См.: container.requirements.general/4. А std::distance возвращает значение знакового целочисленного типа difference_type.

READ ALSO
Как исправить проблему с подключением bass dll к c++ builder?

Как исправить проблему с подключением bass dll к c++ builder?

Пытаюсь подключить bass dll к проекту в c++ builderСкачал архив с

143
C++ Таблица умножения

C++ Таблица умножения

Нужно сделать таблицу 12x12 , почему выводит только первую строку ? Если я сделал вложенный цикл?

141
Глобальное событие смены раскладки клавиатуры

Глобальное событие смены раскладки клавиатуры

Мне нужно знать, какая установлена раскладка клавиатуры пользователя в данный момент (использую в CALLBACK функции)

110
Как получить вектор с нужным capacity?

Как получить вектор с нужным capacity?

Может ли новосозданный вектор после вызова метода reserve(x) иметь capacity больше, чем x? Если да, то правда ли, что чтобы получить вектор с нужным...

140