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

Согласно C11 проекту WG14 версии N1570:

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

Это неопределенное поведение?:

#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 на int)? Другими словами, можно ли char после преобразования в int представить как unsigned char?


Вот как викисловарь определяет понятие "репрезентативный":

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

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

Значения, хранящиеся в объектах без битовых полей любого другого типа объекта, состоят из n × CHAR_BIT бит, где n – размер объекта. этого типа в байтах. Значение может быть скопировано в объект типа unsigned 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 в противном случае определяется реализацией.


person jfs    schedule 10.09.2014    source источник
comment
Я бы сказал, что не указано, является ли это поведением undefined — char может быть беззнаковым, поэтому CHAR_MIN может быть 0. Для знакового символа -1 является допустимым значением, но оно не может быть представлено как unsigned char (оно не входит в диапазон представляемых значений для этого типа).   -  person dyp    schedule 11.09.2014
comment
@dyp: это не указано или определяется реализацией? Предположим, что char это signed (это обычное). я обновлю вопрос   -  person jfs    schedule 11.09.2014
comment
@dyp: signed-ness простого char должен быть задокументирован, поэтому он определяется только реализацией, независимо от того, является ли оно неопределенным поведением или четко определенным.   -  person Deduplicator    schedule 11.09.2014
comment
@Deduplicator Ты прав. Это либо обычный UB, либо определяемый реализацией, независимо от того, является ли он UB.   -  person dyp    schedule 11.09.2014
comment
@dyp: чтобы ответить на мой вопрос в комментарии: в черновике сказано в 6.2.5/15 Реализация должна определять char так, чтобы он имел тот же диапазон, представление и поведение, что и подписанный char или неподписанный char. 45) т. е. он не просто не определен, он определяется реализацией (реализация документирует выбор).   -  person jfs    schedule 11.09.2014
comment
Обоснование этой спецификации заключается в том, что isspace и т. д. могут быть реализованы через массив, например. в типичной системе char const spaces[256] = { 0,0,0,0,0,0,0,0,0,1,0,0,1,0, ... #define isspace(x) ((int)spaces[x]). Хотя на практике я подозреваю, что обычные компиляторы будут поддерживать отрицательные аргументы только потому, что передача отрицательного аргумента является такой распространенной ошибкой.   -  person M.M    schedule 11.09.2014
comment
@MattMcNabb: spaces[(unsigned char)x]   -  person jfs    schedule 11.09.2014
comment
@dyp кажется, что проще сказать, что он не определен, если char signed и хорошо определен в противном случае.   -  person Shafik Yaghmour    schedule 11.09.2014


Ответы (3)


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

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

если мы прочитаем раздел 7.4 Обработка символов ‹ctype.h› из Обоснование международного стандарта — Языки программирования — C, в котором говорится (выделено мной в будущем):

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

Таким образом, действительные значения:

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

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

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

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

person Shafik Yaghmour    schedule 11.09.2014
comment
@ J.F.Sebastian, в этом нет ничего плохого, приведение преобразует знаковый символ (при условии, что он подписан, иначе он ничего не сделает) в диапазон беззнаковых символов с помощью правил в 6.3.1.3 p 2. - person Shafik Yaghmour; 11.09.2014
comment
Я знаю, что §6.3.1.3 упоминается как в моем вопросе, так и в ответе. Я имел в виду, почему (unsigned char) не используется внутри самого isspace(), чтобы избежать неопределенного поведения. - person jfs; 11.09.2014
comment
@ J.F.Sebastian, похоже, избегает дублирования с EOF, что было бы плохо. Это также обеспечивает понятный интерфейс. - person Shafik Yaghmour; 11.09.2014
comment
UB для значения char, переданного как int, не является понятным интерфейсом. Это полная противоположность. Может сработать (работает и с gcc, и с clang в моей системе), может сломать вашу программу или отправить письмо на Марс. Все действительны в соответствии с интерфейсом. Я бы понял аргумент о бескомпромиссной производительности времени, например, (c != EOF) && (_uctype[(unsigned char)c] & _SPACE) мог бы быть медленнее, чем (_ctype + 1)[c] & _S, если бы был доступен более безопасный выбор. - person jfs; 11.09.2014
comment
@ J.F.Sebastian, когда я сказал очистить, я работал с ограничением, согласно которому нам предоставляется выбор между более ограниченным доменом и разрешением возвращать отрицательные значения обратно в домен, поскольку предыдущий обеспечивает более четкий интерфейс. - person Shafik Yaghmour; 12.09.2014
comment
Я могу неправильно понять слово «ясно», но это ужасный дизайн API, когда передача символа в функцию классификации символов может привести к неопределенному поведению. - person jfs; 26.10.2014
comment
@jfs: есть много мест, где стандарт должен был определить предпочтительное и приемлемое поведение, а также макрос, указывающий, что поддерживает реализация. Поддержка всех значений char должна быть предпочтительным поведением, но ограничение поддержки членами набора символов выполнения должно быть приемлемым. Строго соответствующая программа должна иметь возможность использовать #ifdef для определения того, ведет ли себя реализация предпочтительным образом, и отказываться от запуска любой другой. - person supercat; 11.10.2018

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

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

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


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

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

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


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

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

person Deduplicator    schedule 10.09.2014
comment
sizeof(int)==1 безусловно интересно; реализация DS9K может иметь беззнаковый тип символа char, значения которого больше, чем то, что может представлять int. - person dyp; 11.09.2014
comment
Еще один поклонник DS9K, я вижу. Наконец-то они должны доставить мою. - person Deduplicator; 11.09.2014
comment
Не могли бы вы уточнить часть текст, который вы цитируете, не оставляет места для интерпретации? Вопрос в том, что означает слово представляемый. Я могу представить все значения char как unsigned char (формула может зависеть от реализации, но она существует для всех реализаций). Стандарт может использовать другое значение. Не могли бы вы описать это значение? Представьте, что это english.SE, но для программистов. - person jfs; 11.09.2014
comment
@ J.F.Sebastian: сильно изменилось. Теперь лучше? - person Deduplicator; 11.09.2014
comment
@Deduplicator: согласно вашему определению. char можно представить как unsigned char. я обновил вопрос - person jfs; 11.09.2014
comment
@ J.F.Sebastian: char можно представить, поскольку unsigned char не имеет никакого смысла. Только значения могут (или не могут) быть представимы. Согласно моему определению, все значения, представляемые с помощью char, могут быть представлены либо в unsigned char, либо в signed char, что определяется реализацией. - person Deduplicator; 11.09.2014
comment
@Deduplicator: char - это жаргон. Это может означать как тип, так и значение типа в моем вопросе. Цитата из стандарта (§6.2.6.1/4) говорит именно то, что я имею в виду - person jfs; 11.09.2014
comment
char наверняка всегда является типом, а не значением. Представление объекта — это то, как тип отображает значение в байты (и, следовательно, в биты). Интересная вещь, которая, кажется, бросает вас, заключается в том, что байт и (unsigned) char идентичны в C. То, что вы показали, не может ли char быть представлено как unsigned char? но может ли один unsigned char содержать объектное представление значения типа char?. - person Deduplicator; 11.09.2014
comment
@dyp любая реализация sizeof(int)==1 будет иметь UCHAR_MAX > INT_MAX, а не только DS9K - person M.M; 11.09.2014
comment
@Дедупликатор: общий. Вы когда-нибудь говорили, что char меньше CHAR_MAX + 1, что означает, что любое значение типа char меньше CHAR_MAX + 1 (математически)? Обычно набор и член набора называются одним и тем же именем. И я разъяснил, используя прямую ссылку §6.2.6.1/4, смысл, чтобы избежать двусмысленности. - person jfs; 11.09.2014
comment
Нет, не без этого критический каждый. И хотя это распространенная идиома, чтобы назвать одну репрезентативную часть целого, если вы имеете в виду целое, обращение к набору и члену набора одним и тем же именем встречается довольно редко. В любом случае, говоря о стандартах, следует избегать скопированных формулировок. - person Deduplicator; 11.09.2014

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

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

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

Поэтому тип 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'

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

person jfs    schedule 11.09.2014