C указатель на объявление массива с побитовым и оператором


9

Я хочу понять следующий код:

//...
#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. Это моя нынешняя цепочка мыслей:

  1. iscntrl ('a') вызывается и 'a' преобразуется в его целочисленное значение
  2. сначала проверьте, является ли _c -1, затем верните 0, иначе ...
  3. увеличить адрес, на который указывает неопределенный указатель, на 1
  4. объявить этот адрес как указатель на массив длины (без знака) ((int) 'a')
  5. применить побитовый и оператор к _C (0x20) и массиву (???)

Каким-то странным образом это работает, и каждый раз, когда возвращается 0, данный символ _c не является печатным символом. В противном случае, когда она печатается, функция просто возвращает целочисленное значение, которое не представляет особого интереса. Моя проблема понимания заключается в шаге 3, 4 (немного) и 5.

Спасибо за любую помощь.


1
_ctype_по сути, это массив битовых масок. Это индексируется по характеру интереса. Таким образом, _ctype_['A']будет содержать биты, соответствующие «альфе» и «верхнему регистру», _ctype_['a']будет содержать биты, соответствующие «альфе» и «нижнему регистру», _ctype_['1']будет содержать бит, соответствующий «цифре» и т. Д. Похоже, 0x20это бит, соответствующий «управлению» , Но по какой-то причине _ctype_массив смещен на 1, поэтому биты для 'a'действительно в _ctype_['a'+1]. (Это должно было позволить ему работать EOFдаже без дополнительного теста.)
Steve Summit

Бросок (unsigned char)должен позаботиться о том, чтобы символы были подписаны и отрицательные.
Стив Саммит

Ответы:


3

_ctype_Кажется, это ограниченная внутренняя версия таблицы символов, и я предполагаю, + 1что они не удосужились сохранить ее индекс, 0так как он не предназначен для печати. Или, возможно, они используют 1-индексированную таблицу вместо 0-индексированной, как это принято в C.

Стандарт C диктует это для всех функций ctype.h:

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

Пройдемся по коду шаг за шагом:

  • int iscntrl(int _c)Эти intтипы действительно символы, но все функции ctype.h необходимы для ручкиEOF , поэтому они должны быть int.
  • Чек против -1- это чек противEOF , так как она имеет значение -1.
  • _ctype+1 является арифметикой указателя для получения адреса элемента массива.
  • [(unsigned char)_c]это просто доступ к массиву этого массива, где приведено приведение, чтобы обеспечить стандартное требование представления параметра как unsigned char. Обратите внимание, что на charсамом деле может иметь отрицательное значение, так что это защитное программирование. Результат[] доступа массиву является один символ из их внутренней таблицы символов.
  • &Маскировка там , чтобы получить определенную группу символов из таблицы символов. Очевидно, что все символы с установленным битом 5 (маска 0x20) являются управляющими символами. В этом нет смысла без просмотра стола.
  • Все, что установлено с битом 5, вернет значение, замаскированное 0x20, что является ненулевым значением. Это удовлетворяет требованию функции, возвращающей ненулевое значение в случае логического true.

Неправильно, что приведение соответствует стандартному требованию, чтобы значение было представлено как unsigned char. Стандарт требует, чтобы значение уже * было представимо как unsigned charили равно EOFпри вызове подпрограммы. Приведение служит только для «защитного» программирования: исправление ошибки программиста, который передает знак char(или a signed char), когда на них лежит обязанность передать unsigned charзначение при использовании ctype.hмакроса. Следует отметить, что это не может исправить ошибку, когда charзначение -1 передается в реализации, которая использует -1 для EOF.
Эрик Постпищил

Это также предлагает объяснение + 1. Если бы макрос ранее не содержал эту защитную корректировку, то он мог бы быть реализован просто так ((_ctype_+1)[_c] & _C), имея таблицу, проиндексированную со значениями предварительной корректировки от -1 до 255. Таким образом, первая запись не была пропущена и имела смысл. Когда кто-то позже добавил защитное приведение, EOFзначение -1 не будет работать с этим приведением, поэтому они добавили условный оператор для специальной обработки.
Эрик Постпищил

3

_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 не будет работать с этим приведением, поэтому они добавили условный оператор для специальной обработки.
Эрик Постпищил

2

Я начну с шага 3:

увеличить адрес, на который указывает неопределенный указатель, на 1

Указатель не является неопределенным. Это просто определено в каком-то другом модуле компиляции. Вот чтоextern часть говорит компилятору. Поэтому, когда все файлы связаны между собой, компоновщик разрешит ссылки на него.

Так на что это указывает?

Он указывает на массив с информацией о каждом символе. Каждый персонаж имеет свою запись. Запись представляет собой растровое представление характеристик персонажа. Например: если установлен бит 5, это означает, что символ является управляющим символом. Другой пример: если установлен бит 0, это означает, что символ является верхним символом.

Так что что-то вроде (_ctype_ + 1)['x']получит характеристики, которые относятся к'x' . Затем выполняется побитовое выполнение, чтобы проверить, установлен ли бит 5, т.е. проверить, является ли он управляющим символом.

Причиной добавления 1, вероятно, является то, что реальный индекс 0 зарезервирован для какой-то специальной цели.


1

Вся информация здесь основана на анализе исходного кода (и опыта программирования).

Декларация

extern const char *_ctype_;

говорит компилятору, что есть указатель на const charгде-то по имени _ctype_.

(4) Этот указатель доступен как массив.

(_ctype_ + 1)[(unsigned char)_c]

Приведение (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]было бы еще яснее и лаконичнее.
cmaster - восстановить

0

Ключевым моментом здесь является понимание того, что (_ctype_ + 1)[(unsigned char)_c]делает выражение (которое затем передается в побитовое состояние и операции, & 0x20чтобы получить результат!

Краткий ответ: возвращает элемент _c + 1массива, на который указывает_ctype_ .

Как?

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

Чтобы показать, как синтаксис соответствует индексации массива, попробуйте проработать (даже скомпилировать) следующую короткую программу:

#include <stdio.h>
int main() {
    // Code like the following two lines will be defined somewhere in the run-time
    // libraries with which your program is linked, only using _ctype_ in place of _qlist_ ...
    const char list[] = "abcdefghijklmnopqrstuvwxyz";
    const char* _qlist_ = list;
    // These two lines show how expressions like (a)[b] and (a+1)[b] just boil down to
    // a[b] and a[b+1], respectively ...
    char p = (_qlist_)[6];
    char q = (_qlist_ + 1)[6];
    printf("p = %c  q = %c\n", p, q);
    return 0;
}

Не стесняйтесь просить дальнейших разъяснений и / или объяснений.


0

Функции, объявленные в ctype.hпринимают объекты типа int. Для символов, используемых в качестве аргументов, предполагается, что они предварительно приведены к типуunsigned char . Этот символ используется в качестве индекса в таблице, которая определяет характеристику символа.

Кажется, проверка _c == -1используется в том случае, если _cсодержит значение EOF. Если это не так, EOF_c приводится к типу unsigned char, который используется в качестве индекса в таблице, на которую указывает выражение _ctype_ + 1. И если бит указан маской0x20 , установлен, то символ является управляющим символом.

Чтобы понять выражение

(_ctype_ + 1)[(unsigned char)_c]

принять во внимание, что подписка массива является постфиксным оператором, который определяется как

postfix-expression [ expression ]

Вы не можете писать как

_ctype_ + 1[(unsigned char)_c]

потому что это выражение эквивалентно

_ctype_ + ( 1[(unsigned char)_c] )

Итак, выражение _ctype_ + 1 заключено в скобки, чтобы получить первичное выражение.

Так на самом деле у вас есть

pointer[integral_expression]

это дает объект массива по индексу, который вычисляется как выражение, integral_expressionгде указатель (_ctype_ + 1)(gere используется указатель arithmetuc), и integral_expressionэто индекс является выражением (unsigned char)_c.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.