Подтвердить что ты не робот

Обнаружение интегрального переполнения с помощью scanf

Когда в последнее время отвечая на другой вопрос, я обнаружил проблему с кодом вроде:

int n;
scanf ("%d", &n);

С strtol вы можете обнаружить переполнение, потому что в этом случае максимальное допустимое значение вставляется в n и errno установлено для указания переполнения в соответствии с C11 7.22.1.4 The strtol, strtoll, strtoul, and strtoull functions /8:

Если правильное значение находится за пределами диапазона представляемых значений, возвращаются LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX или ULLONG_MAX (в соответствии с типом возврата и знаком значения, если таковые имеются) и значением макрос ERANGE хранится в errno.

Однако в разделах стандарта, касающихся scanf, в частности C11 7.21.6.2 The fscanf function /10, мы видим:

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

Теперь для меня это означает, что любое значение может быть возвращено, и нет упоминания о том, что errno установлено на что угодно. Это обнаружилось, потому что искатель связанного вопроса выше входил 9,999,999,999 в 32-разрядный int и возвращался 1,410,065,407, значение 233 было слишком маленьким, указывая, что оно просто было обернуто на пределе тип.

Когда я попробовал, я вернул 2,147,483,647, максимально возможное 32-разрядное значение без знака.

Итак, мой вопрос таков. Как вы обнаруживаете интегральное переполнение портативным способом при использовании семейства функций scanf? Возможно ли это?

Теперь я должен упомянуть, что в моей системе (Debian 7) errno на самом деле установлен в ERANGE в этих обстоятельствах, но я не могу найти ничего в стандарте, который это задает. Кроме того, возвращаемое значение из scanf равно 1, что указывает на успех при сканировании элемента.

4b9b3361

Ответ 1

Единственный переносимый способ - указать ширину поля, например. с "%4d" (гарантированно даже вписывается в 16-разрядный int) или путем создания строки формата во время выполнения с шириной поля (int)(log(INT_MAX) / log(10)). Это, конечно, также отклоняет, например, 32000, хотя он будет вписываться в 16-разрядный int. Нет, нет никакого портативного способа.

POSIX здесь не указывается больше, не упоминается ERANGE.

В этой manpage упоминается установка errno только в случае возврата EOF; документация glibc не упоминает ERANGE вообще.

Это оставляет вопрос, что предложить новичкам для чтения целых чисел, где я понятия не имею. scanf имеет слишком много undefined и неопределенные аспекты, которые действительно полезны, fgets не может использоваться в продуктивном коде, потому что вы не можете правильно обрабатывать 0-байты, а переносная проверка ошибок с помощью strtol и друзей занимает больше строк, чем реализация (и довольно легко ошибиться). Поведение atoi также undefined для целочисленного переполнения.