Если чтение отрицательного значения в беззнаковое завершается ошибкой через std :: cin (gcc, clang disagree)?

Например,

#include <iostream>

int main() {
  unsigned n{};
  std::cin >> n;
  std::cout << n << ' ' << (bool)std::cin << std::endl;
}

Когда вводится -1, лязг 6.0.0 выводит 0 0, а gcc 7.2.0 выводит 4294967295 1. Интересно, кто прав. А может оба правильные, ибо в стандарте этого не указано? Под неудачей я подразумеваю, что (bool)std::cin будет оценен как ложный. clang 6.0.0 также не может вводить -0.


Начиная с Clang 9.0.0 и GCC 9.2.0, оба компилятора, использующие libstdC++ или libC++ в случае Clang, соглашаются с результатом программы выше, независимо от используемой версии C++ (> = C++ 11), и распечатать

4294967295 1

т.е. они устанавливают значение ULLONG_MAX и не устанавливают бит отказа в потоке.

Что здесь означает «провал»? Вы не можете получить -1, это точно.

Arndt Jonasson 19.04.2018 14:39

Пытались ответить на этот вопрос ... это кроличья нора. Как минимум, вы могли бы добавить, какой стандарт C++ вы компилируете, существует так много изменений «до», «после» и т. д., Что незнание этого сделает почти невозможным дать окончательный ответ.

Richard Critten 19.04.2018 14:42

@ArndtJonasson Я бы предположил, что 'fail' означает, что failbit входного потока был установлен - и поэтому второй выход будет 0, а не 1.

eerorika 19.04.2018 14:48

@RichardCritten Добавлен тег C++ 17.

Lingxi 19.04.2018 14:50

Дополнительные ссылки: ошибка libC++ приводит к изменению поведения и Проблема LWG приводит к изменению C++ 17. Ни из одного из них мне не ясно, следует ли устанавливать failbit.

walnut 25.10.2019 03:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
31
5
1 034
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Предполагаемая семантика вашей команды std::cin >> n описывается здесь (поскольку, по-видимому, для этой операции вызывается std::num_get::get()). В этой функции произошли некоторые семантические изменения, в частности w.r.t. выбор, помещать ли 0 или нет, в C++ 11, а затем снова в C++ 17.

Я не совсем уверен, но считаю, что эти различия могут объяснить различное поведение, которое вы наблюдаете.

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

Я думаю, что оба варианта ошибочны в C++ 171 и что ожидаемый результат должен быть:

4294967295 0

Хотя возвращаемое значение является правильным для последних версий обоих компиляторов, я думаю, что следует установить ios_­base​::​failbit, но я также думаю, что существует путаница в отношении понятия поле для преобразования в стандарте, который может учитывать текущее поведение.

В стандарте написано - [facet.num.get.virtuals # 3.3]:

The sequence of chars accumulated in stage 2 (the field) is converted to a numeric value by the rules of one of the functions declared in the header <cstdlib>:

  • For a signed integer value, the function strtoll.

  • For an unsigned integer value, the function strtoull.

  • For a floating-point value, the function strtold.

Поэтому мы возвращаемся к std::strtoull, который должен возвращать 2ULLONG_MAX , а не устанавливать errno в этом случае (что и делают оба компилятора).

Но в том же блоке (акцент мой):

The numeric value to be stored can be one of:

  • zero, if the conversion function does not convert the entire field.

  • the most positive (or negative) representable value, if the field to be converted to a signed integer type represents a value too large positive (or negative) to be represented in val.

  • the most positive representable value, if the field to be converted to an unsigned integer type represents a value that cannot be represented in val.

  • the converted value, otherwise.

The resultant numeric value is stored in val. If the conversion function does not convert the entire field, or if the field represents a value outside the range of representable values, ios_­base​::​failbit is assigned to err.

Обратите внимание, что все это говорит о "поле для преобразования", а не о фактическом значении, возвращаемом std::strtoull. Поле здесь представляет собой расширенную последовательность символов '-', '1'.

Поскольку поле представляет значение (-1), которое не может быть представлено unsigned, возвращаемое значение должно быть UINT_MAX, а бит отказа должен быть установлен на std::cin.


1clang was actually right prior to C++17 because the third bullet in the above quote was:

- the most negative representable value or zero for an unsigned integer type, if the field represents a value too large negative to be represented in val. ios_base::failbit is assigned to err.

2std::strtoull returns ULLONG_MAX because (thanks @NathanOliver) — C/7.22.1.4.5:

If the subject sequence has the expected form and the value of base is zero, the sequence of characters starting with the first digit is interpreted as an integer constant according to the rules of 6.4.4.1. [...] If the subject sequence begins with a minus sign, the value resulting from the conversion is negated (in the return type).

Я полагаю, что вы ищете последовательность символов, начинающаяся с первой цифры, интерпретируется как целочисленная константа в соответствии с правилами п. 6.4.4.1. с Если субъектная последовательность начинается со знака минус, значение, полученное в результате преобразования, инвертируется (в возвращаемом типе) из 7.22.1.4.5 стандарта C. Думаю, на этом можно было бы сделать этот ответ «стандартно полным» :)

NathanOliver 19.04.2018 15:04

@NathanOliver Я добавил это, но на самом деле я переписываю ответ, потому что я нашел некоторые другие доказательства в стандарте - я удаляю его, пока редактирую. В любом случае спасибо за цитату!

Holt 19.04.2018 15:05

@NathanOliver Я обновил ответ, я был бы рад узнать вашу точку зрения на него.

Holt 19.04.2018 15:13

Я не уверен, где вы взяли вторую цитату, но она не соответствует тому, что у меня есть для C++ 17: timsong-cpp.github.io/cppwp/facet.num.get.virtuals#3.3. Соответственно, это должно быть самое положительное значение.

NathanOliver 19.04.2018 15:24

Ах, взглянул на мой черновик C++ 11, и это язык там. Похоже, где-то по ходу дела произошли изменения.

NathanOliver 19.04.2018 15:26

@NathanOliver N4296 (последний черновик C++ 14, если я не ошибаюсь), я обновлю вашу цитату, поскольку этот вопрос помечен как C++ 17. Я не думаю, что это меняет мой вывод.

Holt 19.04.2018 15:27

Ага, я все еще думал, что он должен хранить максимум в n, но iirc, если установлен failbit, значение, переданное в cin, устанавливается на 0. +1

NathanOliver 19.04.2018 15:32

@NathanOliver Установлен битовый бит, не следует ли не трогать значение вместо того, чтобы установить его на 0?

Lingxi 19.04.2018 15:40

@NathanOliver Я думаю, что вы на самом деле правы и что n должен быть равен UINT_MAX, но бит отказа должен быть установлен, поэтому оба компилятора будут ошибаться?

Holt 19.04.2018 15:42

@Lingxi и holt, по крайней мере, это говорит, что он должен быть установлен в 0.

NathanOliver 19.04.2018 15:59

@NathanOliver Я не нашел ссылки в это, и я просто вспомнил, почему я никогда не отвечал на вопросы, связанные с потоками на C++;)

Holt 19.04.2018 16:01

Без шуток. Потоки - это PITA.

NathanOliver 19.04.2018 16:04

@NathanOliver На самом деле, я думаю, что cppreference (и связанный ответ) ссылаются на первую пулю выше «ноль, если функция преобразования не преобразует все поле»., которая происходит, если ввод не содержит действительного числа.

Holt 19.04.2018 16:05

В этом есть большой смысл. Если это так, то оба неверны, так как на выходе должно быть 4294967295 0: coliru.stacked-crooked.com/a/ea3475f2633adcd9. Жаль, что я снова не могу +1

NathanOliver 19.04.2018 16:09

В конце вы говорите, что бит ошибки должен быть установлен, но в начале вы говорите, что этого не следует ...

wizzwizz4 19.04.2018 19:18

@ Холт Да ладно. Я записал то, что вы сказали.

wizzwizz4 19.04.2018 19:37

Обратите внимание, что Пример strtoul в cppreference без проблем преобразует -40 в беззнаковый, так почему же -1 может быть ошибкой?

Bo Persson 20.04.2018 09:59

@BoPersson Об этом упоминается в моем ответе - поведение strtoull ожидается, но возвращаемое значение std::num_get::get не является напрямую преобразованным значением, возвращаемым strtoull, что я обсуждаю в конце своего ответа.

Holt 20.04.2018 10:10

@BoPersson В частности, цитата «наиболее положительное представимое значение, если поле быть преобразованным для целочисленного типа без знака представляет значение, которое не может быть представлено в val». подразумевает (с моей точки зрения) проверку фактической последовательности символов, а не только возвращаемого значения strtoull.

Holt 20.04.2018 10:12

Другое правдоподобное прочтение состоит в том, что значение, представленное «полем для преобразования», является значением, определенным по правилам strto*, что делает GCC правильным.

T.C. 24.04.2018 23:35

@ T.C. Я согласен, что это не на 100% однозначно, но зачем в этом случае использовать «поле для преобразования» вместо «преобразованное значение»? Более того, даже в этом случае gcc, вероятно, будет ошибаться, поскольку преобразованная функция - это strtoull, которая вернет ULLONG_MAX, который, вероятно, не может быть представлен беззнаковым int.

Holt 25.04.2018 00:41

@Holt Это возникло в связи с другим вопросом, и я заметил, что последние версии GCC и Clang согласны с этим, но не согласны с вашим ответом. Не могли бы вы вернуться к нему?

walnut 20.10.2019 22:26

@uneven_mark Можете указать мне на другой вопрос? Я не заметил изменений в стандарте, которые могли бы повлиять на это, поэтому в настоящее время я буду настаивать на этом (даже после того, как перечитал это в 10-й раз), если не будет доказано обратное или если нет каких-либо «официальных» аргументов от gcc и лязг.

Holt 23.10.2019 09:51

@Holt Эта проблема на самом деле не обсуждалась, но в этот вопрос код работал бы так, как предполагалось, если бы бит отказа был установлен для отрицательных входов, хотя OP этого вопроса, похоже, неправильно понял unsigned в целом. Я был удивлен поведением, когда сам проверил его и искал связанный вопрос, и в итоге оказался здесь.

walnut 23.10.2019 11:16

Поскольку это кажется очень простым вопросом при выполнении операций ввода-вывода, я подумал, что стоит попросить разъяснений. Может быть, кто-то другой предоставит рассуждения с точки зрения стандартных библиотек для поведения, особенно libC++, который, похоже, изменился с момента первоначальной публикации этого вопроса.

walnut 23.10.2019 11:19

@Holt Здесь - это комментарий в отчете об ошибке, который вызвал изменение в поведении libC++, обсуждая, следует ли устанавливать бит отказа. Я не знаю, добавит ли это что-нибудь новое в эту ветку.

walnut 25.10.2019 03:21

@uneven_mark Я думаю, это текущий стандарт. Если вы прочитаете стандарт для функций C strto*, вы заметите, насколько сложно их понять (просто проверьте цитату здесь). С моей точки зрения, и gcc, и clang теперь следуют спецификации функций C для возвращаемого значения (см. github.com/llvm-mirror/libcxx/commit/… для clang). [...]

Holt 25.10.2019 09:18

@uneven_mark [...] Но насколько я понимаю, при проверке, нужно ли устанавливать errnum_get, см., например, cplusplus.github.io/LWG/lwg-defects.html#1169), есть упоминание о поле для преобразования, которое быть заставляет меня думать, что это лишнее Проверки должны выполняться num_get, независимо от состояния errno или значения, возвращаемого strto*.

Holt 25.10.2019 09:21

Речь идет о различиях между реализациями библиотек и - и не столько о различиях между компиляторами (, ).

cppreference довольно хорошо устраняет эти несоответствия:

The result of converting a negative number string into an unsigned integer was specified to produce zero until , although some implementations followed the protocol of std::strtoull which negates in the target type, giving ULLONG_MAX for "-1", and so produce the largest value of the target type instead. As of , strictly following std::strtoull is the correct behavior.

Это сводится к:

  • ULLONG_MAX (4294967295) верен в будущем, поскольку (оба компилятора делают это правильно сейчас)
  • Раньше это должен был быть 0 со строгим чтением стандарта ()
  • Некоторые реализации (в частности, ) следовали протоколу std::strtoull вместо этого (что теперь считается правильным поведением)

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

Интересный блок текста гласит (при условии, что версия до C++ 17):

If the conversion function results in a negative value too large to fit in the type of v, the most negative representable value is stored in v, or zero for unsigned integer types.

В соответствии с этим указано значение 0. Кроме того, нигде не указано, что это должно привести к установке биты отказа.

Верхний ответ говорит то же самое о наборе значений. Однако он дополнительно утверждает, что бит отказа должен быть установлен в обоих случаях, что не то, что делают текущие компиляторы. На странице cppreference явно не упоминается, следует ли его устанавливать. Это должно означать, что его не следует устанавливать ни в том, ни в другом случае? Тогда старое поведение Clang тоже было бы неправильным. Я думаю, это то, что нужно подробно объяснить.

walnut 22.10.2019 00:15

@uneven_mark я обновил свой ответ - оказалось, что clang сделал то же самое, что и gcc

darune 22.10.2019 09:37

Это проблема библиотеки, поэтому на самом деле вместо gcc vs clang мы должны говорить о libstdC++ vs libC++. Godbolt по умолчанию использует libstdC++ для обоих. Вам нужно указать -stdlib=libc++, тогда вы увидите результаты OP. Я думаю, что необходимо более подробное объяснение того, почему не следует устанавливать failbit, учитывая, что это вопрос языкового юриста, и ответ, получивший большое количество голосов, противоречит как C++ 17, так и более ранним версиям.

walnut 22.10.2019 10:27

@uneven_mark я почти уверен, что это ошибка в libC++ (вам не кажется?) - язык-юрист или нет

darune 22.10.2019 13:38

Не знаю, так ли это. Как вы можете видеть в верхнем ответе, было обсуждение того, что на самом деле означает стандарт, поскольку в нем написано «Если функция преобразования не преобразует все поле или поле представляет значение за пределами диапазона представимых значений, ios_base :: failbit присваивается err.». Стандартные библиотеки, похоже, теперь согласны с тем, что это не означает, что в этой ситуации должен быть установлен отказоустойчивый бит, но снова главный ответ утверждает иначе. Я сам не знаю, какой правильный ответ.

walnut 22.10.2019 14:00

Возможно, позже я найду соответствующий патч для libC++. Он может содержать как минимум причину изменения разработчика libC++ (исправление ошибки?)

walnut 22.10.2019 14:01

@uneven_mark где мы их находим? Я мог сказать, что они «исправили ошибку» с 6000 до 7000, проведя тестирование, проведенное мной.

darune 22.10.2019 14:04

Я бы начал с поиска в их bugzilla закрытой ошибки здесь: bugs.llvm.org Если бы я ничего не нашел, я бы попытался выяснить соответствующий код в библиотеке и git-bisect для изменения. Мы надеемся, что сообщение о фиксации будет полезным. Однако это может занять много времени.

walnut 22.10.2019 14:11

Я связал ошибку libC++, которую нашел в комментариях к вопросу.

walnut 25.10.2019 03:11

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