Я хочу понять следующий код:
//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}
Он происходит из файла ctype.h из исходного кода операционной системы obenbsd. Эта функция проверяет, является ли символ контрольным символом или печатной буквой в диапазоне ASCII. Это моя нынешняя цепочка мыслей:
- iscntrl ('a') вызывается и 'a' преобразуется в его целочисленное значение
- сначала проверьте, является ли _c -1, затем верните 0, иначе ...
- увеличить адрес, на который указывает неопределенный указатель, на 1
- объявить этот адрес как указатель на массив длины (без знака) ((int) 'a')
- применить побитовый и оператор к _C (0x20) и массиву (???)
Каким-то странным образом это работает, и каждый раз, когда возвращается 0, данный символ _c не является печатным символом. В противном случае, когда она печатается, функция просто возвращает целочисленное значение, которое не представляет особого интереса. Моя проблема понимания заключается в шаге 3, 4 (немного) и 5.
Спасибо за любую помощь.
_ctype_
по сути, это массив битовых масок. Это индексируется по характеру интереса. Таким образом,_ctype_['A']
будет содержать биты, соответствующие «альфе» и «верхнему регистру»,_ctype_['a']
будет содержать биты, соответствующие «альфе» и «нижнему регистру»,_ctype_['1']
будет содержать бит, соответствующий «цифре» и т. Д. Похоже,0x20
это бит, соответствующий «управлению» , Но по какой-то причине_ctype_
массив смещен на 1, поэтому биты для'a'
действительно в_ctype_['a'+1]
. (Это должно было позволить ему работатьEOF
даже без дополнительного теста.)(unsigned char)
должен позаботиться о том, чтобы символы были подписаны и отрицательные.Ответы:
_ctype_
Кажется, это ограниченная внутренняя версия таблицы символов, и я предполагаю,+ 1
что они не удосужились сохранить ее индекс,0
так как он не предназначен для печати. Или, возможно, они используют 1-индексированную таблицу вместо 0-индексированной, как это принято в C.Стандарт C диктует это для всех функций ctype.h:
Пройдемся по коду шаг за шагом:
int iscntrl(int _c)
Этиint
типы действительно символы, но все функции ctype.h необходимы для ручкиEOF
, поэтому они должны бытьint
.-1
- это чек противEOF
, так как она имеет значение-1
._ctype+1
является арифметикой указателя для получения адреса элемента массива.[(unsigned char)_c]
это просто доступ к массиву этого массива, где приведено приведение, чтобы обеспечить стандартное требование представления параметра какunsigned char
. Обратите внимание, что наchar
самом деле может иметь отрицательное значение, так что это защитное программирование. Результат[]
доступа массиву является один символ из их внутренней таблицы символов.&
Маскировка там , чтобы получить определенную группу символов из таблицы символов. Очевидно, что все символы с установленным битом 5 (маска 0x20) являются управляющими символами. В этом нет смысла без просмотра стола.источник
unsigned char
. Стандарт требует, чтобы значение уже * было представимо какunsigned char
или равноEOF
при вызове подпрограммы. Приведение служит только для «защитного» программирования: исправление ошибки программиста, который передает знакchar
(или asigned char
), когда на них лежит обязанность передатьunsigned char
значение при использованииctype.h
макроса. Следует отметить, что это не может исправить ошибку, когдаchar
значение -1 передается в реализации, которая использует -1 дляEOF
.+ 1
. Если бы макрос ранее не содержал эту защитную корректировку, то он мог бы быть реализован просто так((_ctype_+1)[_c] & _C)
, имея таблицу, проиндексированную со значениями предварительной корректировки от -1 до 255. Таким образом, первая запись не была пропущена и имела смысл. Когда кто-то позже добавил защитное приведение,EOF
значение -1 не будет работать с этим приведением, поэтому они добавили условный оператор для специальной обработки._ctype_
указатель на глобальный массив из 257 байтов Я не знаю, для чего_ctype_[0]
используется._ctype_[1]
через_ctype_[256]_
представляют категории символов символов 0,…, 255 соответственно:_ctype_[c + 1]
представляет категорию символаc
. Это то же самое, что сказать, что_ctype_ + 1
указывает на массив из 256 символов, где(_ctype_ + 1)[c]
представляет категорию символаc
.(_ctype_ + 1)[(unsigned char)_c]
не является декларацией Это выражение, использующее оператор индекса массива. Это доступ к позиции(unsigned char)_c
массива, которая начинается с(_ctype_ + 1)
.Преобразование кода
_c
изint
вunsigned char
не является строго обязательным: функции ctype принимают приведенные значенияunsigned char
(char
подписаны в OpenBSD): правильный вызовchar c; … iscntrl((unsigned char)c)
. У них есть преимущество, гарантирующее отсутствие переполнения буфера: если приложение вызываетiscntrl
со значением, которое находится за пределами диапазонаunsigned char
и не равно -1, эта функция возвращает значение, которое может быть не значимым, но, по крайней мере, не приведет к сбой или утечка личных данных, которые оказались по адресу за пределами границ массива. Значение даже правильно, если функция вызываетсяchar c; … iscntrl(c)
до тех пор,c
пока не -1.Причиной особого случая с -1 является то, что это
EOF
. Многие стандартные функции C, которые работаютchar
, напримерgetchar
, с символом, представляют символ какint
значение, которое является значением char, заключенным в положительный диапазон, и используют специальное значение,EOF == -1
чтобы указать, что ни один символ не может быть прочитан. Для таких функций , какgetchar
,EOF
указывает на конец файла, отсюда и название е nd- о f- е Ile. Эрик Постпишил предполагает, что код изначально был простоreturn _ctype_[_c + 1]
, и это, вероятно, правильно:_ctype_[0]
было бы значение для EOF. Эта более простая реализация приводит к переполнению буфера, если функция используется неправильно, тогда как текущая реализация избегает этого, как обсуждалось выше.If
v
- значение, найденное в массиве,v & _C
проверяет, установлен ли бит0x20
вv
. Значения в массиве - это маски категорий, в которых находится символ:_C
устанавливается для управляющих символов,_U
устанавливается для заглавных букв и т. Д.источник
(_ctype_ + 1)[_c]
будет использовать правильный индекс массива , как указано в стандарте C, потому что это ответственность пользователя , чтобы передать либоEOF
илиunsigned char
значение. Поведение для других значений не определяется стандартом C. Приведение не служит для реализации поведения, требуемого стандартом C. Это обходной путь для защиты от ошибок, вызванных тем, что программисты неправильно передают отрицательные значения символов. Тем не менее, он является неполным или неправильным (и не может быть исправлен), поскольку значение символа -1 будет обязательно рассматриваться какEOF
.+ 1
. Если бы макрос ранее не содержал эту защитную корректировку, то он мог бы быть реализован просто так((_ctype_+1)[_c] & _C)
, имея таблицу, проиндексированную со значениями предварительной корректировки от -1 до 255. Таким образом, первая запись не была пропущена и имела смысл. Когда кто-то позже добавил защитное приведение,EOF
значение -1 не будет работать с этим приведением, поэтому они добавили условный оператор для специальной обработки.Я начну с шага 3:
Указатель не является неопределенным. Это просто определено в каком-то другом модуле компиляции. Вот что
extern
часть говорит компилятору. Поэтому, когда все файлы связаны между собой, компоновщик разрешит ссылки на него.Так на что это указывает?
Он указывает на массив с информацией о каждом символе. Каждый персонаж имеет свою запись. Запись представляет собой растровое представление характеристик персонажа. Например: если установлен бит 5, это означает, что символ является управляющим символом. Другой пример: если установлен бит 0, это означает, что символ является верхним символом.
Так что что-то вроде
(_ctype_ + 1)['x']
получит характеристики, которые относятся к'x'
. Затем выполняется побитовое выполнение, чтобы проверить, установлен ли бит 5, т.е. проверить, является ли он управляющим символом.Причиной добавления 1, вероятно, является то, что реальный индекс 0 зарезервирован для какой-то специальной цели.
источник
Вся информация здесь основана на анализе исходного кода (и опыта программирования).
Декларация
говорит компилятору, что есть указатель на
const char
где-то по имени_ctype_
.(4) Этот указатель доступен как массив.
Приведение
(unsigned char)_c
гарантирует, что значение индекса находится в диапазонеunsigned char
(0..255).Арифметика указателя
_ctype_ + 1
эффективно сдвигает позицию массива на 1 элемент. Я не знаю, почему они реализовали массив таким образом. Использование диапазона_ctype_[1]
.._ctype[256]
для значений символов0
..255
оставляет значение_ctype_[0]
неиспользованным для этой функции. (Смещение 1 может быть реализовано несколькими альтернативными способами.)Доступ к массиву извлекает значение (типа
char
, для экономии места), используя символьное значение в качестве индекса массива.(5) Битовая операция И извлекает один бит из значения.
Очевидно, значение из массива используется в качестве битового поля, где бит 5 (считая от 0, начиная с младшего значащего бита, =
0x20
) является флагом для «является управляющим символом». Таким образом, массив содержит значения битовых полей, описывающих свойства символов.источник
+ 1
указатель, чтобы прояснить, что они обращаются к элементам1..256
вместо1..255,0
._ctype_[1 + (unsigned char)_c]
было бы эквивалентно из-за неявного преобразования вint
. И_ctype_[(_c & 0xff) + 1]
было бы еще яснее и лаконичнее.Ключевым моментом здесь является понимание того, что
(_ctype_ + 1)[(unsigned char)_c]
делает выражение (которое затем передается в побитовое состояние и операции,& 0x20
чтобы получить результат!Краткий ответ: возвращает элемент
_c + 1
массива, на который указывает_ctype_
.Как?
Во-первых, хотя вы, кажется, думаете, что
_ctype_
это неопределенно, на самом деле это не так! Заголовок объявляет его как внешнюю переменную, но он определен (почти наверняка) в одной из библиотек времени выполнения, с которыми связана ваша программа при ее создании.Чтобы показать, как синтаксис соответствует индексации массива, попробуйте проработать (даже скомпилировать) следующую короткую программу:
Не стесняйтесь просить дальнейших разъяснений и / или объяснений.
источник
Функции, объявленные в
ctype.h
принимают объекты типаint
. Для символов, используемых в качестве аргументов, предполагается, что они предварительно приведены к типуunsigned char
. Этот символ используется в качестве индекса в таблице, которая определяет характеристику символа.Кажется, проверка
_c == -1
используется в том случае, если_c
содержит значениеEOF
. Если это не так,EOF
_c приводится к типу unsigned char, который используется в качестве индекса в таблице, на которую указывает выражение_ctype_ + 1
. И если бит указан маской0x20
, установлен, то символ является управляющим символом.Чтобы понять выражение
принять во внимание, что подписка массива является постфиксным оператором, который определяется как
Вы не можете писать как
потому что это выражение эквивалентно
Итак, выражение
_ctype_ + 1
заключено в скобки, чтобы получить первичное выражение.Так на самом деле у вас есть
это дает объект массива по индексу, который вычисляется как выражение,
integral_expression
где указатель(_ctype_ + 1)
(gere используется указатель arithmetuc), иintegral_expression
это индекс является выражением(unsigned char)_c
.источник