Что означает "представимый" в C11?

В соответствии с C11 проект №1515 для WG14:

Заголовок <ctype.h> объявляет несколько функций, полезных для классификации и отображения символов. Во всех случаях аргумент равен int, значение которого должно быть представлено как unsigned char или должно равное значению макроса EOF. Если аргумент имеет любое другое значение, поведение undefined.

Это поведение undefined?:

#include <ctype.h>
#include <limits.h>
#include <stdlib.h>

int main(void) {
  char c = CHAR_MIN; /* let assume that char is signed and CHAR_MIN < 0 */
  return isspace(c) ? EXIT_FAILURE : EXIT_SUCCESS;
}

Позволяет ли стандарт пропускать char до isspace() (char to int)? Другими словами, char после преобразования в int, представляемого как unsigned char?


Здесь wiktionary определяет "представимый" :

Способность быть представленным.

Может ли char быть представленным как unsigned char? Да. §6.2.6.1/4:

Значения, хранящиеся в объектах без битового поля любого другого типа объекта состоят из n × CHAR_BIT бит, где n - размер объекта этого тип, в байтах. Значение может быть скопировано в объект типа без знака char [n] (например, memcpy); результирующий набор байтов называемое представлением объекта значения.

sizeof(char) == 1 поэтому его представление объекта unsigned char[1], т.е. char может быть представлено как unsigned char. Где я ошибаюсь?

Конкретный пример, я могу представить [-2, -1, 0, 1] как [0, 1, 2, 3]. Если я не могу тогда почему?


Связано: Согласно §6.3.1.3 isspace((unsigned char)c) является переносимым, если INT_MAX >= UCHAR_MAX в противном случае это определяется реализацией.

Ответ 1

В предположении, что char будет подписано, это будет undefined поведение, в противном случае это четко определено, так как CHAR_MIN будет имеют значение 0. Легче увидеть намерение и смысл:

значение которого должно быть представлено как unsigned char или должно равное значению макроса EOF

если мы прочитаем раздел 7.4 Обработка символов < ctype.h > из Обоснование для языков международного стандартного программирования-C, в котором говорится (акцент мой вперед):

Поскольку эти функции часто используются прежде всего как макросы, их домен ограничивается небольшими положительными целыми числами, представленными в unsigned char, плюс значение EOF. EOF традиционно -1, но может любое отрицательное целое число и, следовательно, отличное от любого действительного символьный код. Таким образом, эти макросы могут быть эффективно реализованы посредством используя аргумент как индекс в небольшой массив атрибутов.

Допустимые значения:

  • Положительные целые числа, которые могут вписываться в unsigned char
  • EOF, который представляет собой некоторое определенное заданное отрицательное число

Несмотря на то, что это соображение C99, поскольку конкретная формулировка, на которую вы ссылаетесь, не изменяется от C99 до C11, и поэтому обоснование все еще подходит.

Мы также можем найти, почему интерфейс использует int как аргумент, а не char, из раздела 7.1.4 Использование библиотечных функций:

Все прототипы библиотеки указаны в терминах "расширенных" типоваргумент, ранее объявленный как char, теперь записывается как int. Эта гарантирует, что большинство функций библиотеки можно вызвать с помощью или без прототипа в области, поддерживая обратную совместимость с pre-C89. Обратите внимание, однако, что поскольку такие функции, как printf и scanf используют списки аргументов переменной длины, они должны быть вызваны в объем прототипа.

Ответ 2

Что означает представление в типе?

Повторно сформулированный тип - это соглашение, для которого означают базовые битовые шаблоны. Таким образом, значение представляется в типе, если этот тип присваивает некоторый бит-шаблон, который имеет значение.

Преобразование (которое может потребоваться приведение), является отображением из значения (представленного определенным типом) в значение (возможно, другое), представленное в целевом типе.


При данном предположении (что char подписано), CHAR_MIN безусловно отрицательно, и текст, который вы цитируете, не оставляет места для интерпретации:
Да, это поведение undefined, так как unsigned char не может представлять никаких отрицательных чисел.

Если это предположение не выполнялось, ваша программа была бы корректно определена, поскольку CHAR_MIN будет 0, допустимым значением для unsigned char.

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


В стороне, нет гарантии, что sizeof(int)>1 или INT_MAX >= CHAR_MAX, поэтому int, возможно, не сможет представить все возможные значения для unsigned char.

Поскольку конверсии определены как сохраняющие значения, подписанный char всегда может быть преобразован в int.
Но если это было отрицательно, это не меняет невозможности представления отрицательного значения как unsigned char. (Преобразование определено, так как преобразование из любого интегрального типа в любой интегральный тип unsigned всегда определяется, хотя сужение конверсий требует приведения.)

Ответ 3

Показательная цитата (для меня) - это §6.3.1.3/1:

если значение может быть представлено новым типом, оно не изменяется.

i.e., если значение должно быть изменено, значение не может быть представлено новым типом.

Поэтому тип unsigned не может представлять отрицательное значение.

Чтобы ответить на вопрос в названии: "представимый" означает "может быть представлен" из п. 6.3.1.3 и не связан с "представлением объектов" из §6.2.6.1.

Кажется тривиальным в ретроспективе. Возможно, меня смутило привычка рассматривать b'\xFF', 0xff, 255, -1 как один и тот же байт в Python:

>>> (255).to_bytes(1, 'big')
b'\xff'
>>> int.from_bytes(b'\xFF', 'big')
255
>>> 255 == 0xff
True
>>> (-1).to_bytes(1, 'big', signed=True)
b'\xff'

и неверие в то, что поведение undefined передает символ в функцию классификации символов, например, isspace(CHAR_MIN).