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 else ...
  3. приріст адреси на невизначений покажчик вказує на 1
  4. оголосити цю адресу як вказівник на масив довжини (неподписаний знак) ((int) 'a')
  5. застосувати побітові та оператор до _C (0x20) та масиву (???)

Якимось чином, як не дивно, він працює і кожного разу, коли 0 повертається, даний char _c не є символом для друку. Інакше при друкуванні функція просто повертає ціле значення, яке не представляє особливого інтересу. Моя проблема розуміння полягає в кроці 3, 4 (трохи) та 5.

Дякую за будь-яку допомогу.


1
_ctype_по суті є масивом бітових масок. Він індексується характером, що цікавить. Таким чином , _ctype_['A']буде містити біти , що відповідають «альфа» і «великі букви», _ctype_['a']буде містити біти , що відповідають «альфа» і «нижній регістр», _ctype_['1']буде містити біт , відповідне «цифру», і т.д. Схоже , 0x20це біт , відповідні «контроль» . Але чомусь _ctype_масив компенсується на 1, тому біти для 'a'насправді є _ctype_['a'+1]. (Це, мабуть, дозволило йому працювати EOFнавіть без зайвих випробувань.)
Стів Саміт

У ролі (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, що є ненульовим значенням. Це задовольняє вимогу функції, що повертається не нульовою у випадку булевого істини.

Неправильно, що амплітуда задовольняє стандартну вимогу, щоб значення було представним як unsigned char. Стандарт вимагає, щоб значення вже * було представлене як unsigned charабо рівне EOF, коли викликається рутина. Заголовок служить лише "оборонним" програмуванням: виправлення помилки програміста, який передає підписаний char(або а signed char), коли натискання було на них, щоб передати unsigned charзначення при використанні ctype.hмакросу. Слід зазначити, що ця помилка не може виправити помилку, коли charзначення −1 передається в реалізації, для якої використовується -1 EOF.
Eric Postpischil

Це також пропонує пояснення + 1. Якщо раніше макрос не містив цього захисного коригування, він міг би бути реалізований просто так ((_ctype_+1)[_c] & _C), маючи таблицю, індексовану зі значеннями попереднього коригування від 1 до 255. Отже, перший запис не був пропущений і слугував цілі. Коли пізніше хтось додав захисний склад, EOFзначення −1 не працюватиме з цим кидком, тому вони додали умовного оператора, щоб він спеціально ставився до цього.
Eric Postpischil

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 приймають значення char unsigned char( charпідписано на OpenBSD): правильний виклик char c; … iscntrl((unsigned char)c). Вони мають перевагу гарантувати відсутність переповнення буфера: якщо програма звертається iscntrlзі значенням, яке знаходиться поза діапазоном unsigned charта не -1, ця функція повертає значення, яке може бути не значимим, але принаймні не спричинить збій або витік приватних даних, що трапився за адресою поза межами масиву. Значення навіть правильне, якщо функція викликається до char c; … iscntrl(c)тих пір, cпоки не -1.

Причина особливого випадку з -1 полягає в тому, що це EOF. charНаприклад getchar, багато стандартних функцій C, які працюють на a , представляють символ як intзначення, яке є знаком char, заверненим у позитивний діапазон, і використовують спеціальне значення, EOF == -1щоб вказати, що жоден символ не може бути прочитаний. Для таких функцій , як getchar, EOFвказує на кінець файлу, звідси і назва е nd- про f- е Ile. Ерік Postpischil припускає, що код був спочатку справедливим return _ctype_[_c + 1], і це, мабуть, правильно: _ctype_[0]було б значенням для EOF. Ця більш проста реалізація призводить до переповнення буфера, якщо функція не використовується, тоді як поточна реалізація дозволяє уникнути цього, як обговорювалося вище.

Якщо vзначення, знайдене в масиві, v & _Cтестує, чи встановлено біт 0x20у v. Значення в масиві - це маски категорій, в яких знаходиться символ: _Cвстановлюється для керуючих символів, _Uвстановлюється для великих літер тощо.


(_ctype_ + 1)[_c] використовував би правильний індекс масиву, визначений стандартом C, оскільки відповідальність за користування передає EOFабо unsigned charзначення. Поведінка для інших значень не визначається стандартом C. Акторський склад не служить для реалізації поведінки, що вимагається стандартом C. Це рішення, яке використовується для захисту від помилок, викликаних програмістами, неправильно передаючими негативні значення символів. Однак воно неповне або неправильне (і не може бути виправлене), оскільки значення -1 символу обов'язково трактується як EOF.
Eric Postpischil

Це також пропонує пояснення + 1. Якщо раніше макрос не містив цього захисного коригування, він міг би бути реалізований просто так ((_ctype_+1)[_c] & _C), маючи таблицю, індексовану зі значеннями попереднього коригування від 1 до 255. Отже, перший запис не був пропущений і слугував цілі. Коли пізніше хтось додав захисний склад, EOFзначення −1 не працюватиме з цим кидком, тому вони додали умовного оператора, щоб він спеціально ставився до цього.
Eric Postpischil

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) Побітна операція AND дістає один біт зі значення.

Мабуть, значення з масиву використовується як бітове поле, де біт 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 приводиться до типу безпідписаного символу, який використовується як індекс у таблиці, на яку вказує вираз _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.