Chcę zrozumieć następujący kod:
//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}
Pochodzi z pliku ctype.h z kodu źródłowego systemu operacyjnego obenbsd. Ta funkcja sprawdza, czy znak jest znakiem kontrolnym, czy drukowaną literą w zakresie ascii. Oto mój obecny ciąg myśli:
- Wywoływana jest funkcja iscntrl („a”), a „a” jest konwertowane na wartość całkowitą
- najpierw sprawdź, czy _c ma wartość -1, a następnie zwróć 0 w przeciwnym razie ...
- zwiększ adres niezdefiniowanego wskaźnika o 1
- zadeklaruj ten adres jako wskaźnik do tablicy o długości (znak bez znaku) ((int) „a”)
- zastosuj bitowy i operator do _C (0x20) i tablicy (???)
Co dziwne, działa i za każdym razem, gdy zwracane jest 0, dany znak _c nie jest znakiem do wydrukowania. W przeciwnym razie, gdy będzie można go wydrukować, funkcja po prostu zwraca wartość całkowitą, która nie jest przedmiotem szczególnego zainteresowania. Mój problem ze zrozumieniem znajduje się w kroku 3, 4 (trochę) i 5.
Dziękuję za wszelką pomoc.
_ctype_
jest zasadniczo tablicą masek bitowych. Jest indeksowany według charakteru zainteresowania. Więc_ctype_['A']
zawierałby bity odpowiadające „alfa” i „wielkimi literami”,_ctype_['a']
zawierałby bity odpowiadające „alfa” i „małymi literami”,_ctype_['1']
zawierałby bit odpowiadający „cyfrze” itp. Wygląda na0x20
to, że jest to bit odpowiadający „kontroli” . Ale z jakiegoś powodu_ctype_
tablica jest przesunięta o 1, więc bity dla'a'
są naprawdę w_ctype_['a'+1]
. (Prawdopodobnie miało to pozwolić, aby działałoEOF
nawet bez dodatkowego testu.)(unsigned char)
polega na tym, aby postacie były podpisane i negatywne.Odpowiedzi:
_ctype_
wydaje się być ograniczoną wewnętrzną wersją tabeli symboli i domyślam się+ 1
, że nie zadali sobie trudu zapisania jej indeksu0
, ponieważ nie można jej wydrukować. Lub być może używają tabeli 1-indeksowanej zamiast 0-indeksowanej, co jest niestandardowe w C.Standard C dyktuje to dla wszystkich funkcji ctype.h:
Przeglądając kod krok po kroku:
int iscntrl(int _c)
Teint
typy są naprawdę znaków, ale wszystkie funkcje ctype.h są zobowiązani do uchwytuEOF
, więc muszą byćint
.-1
jest sprawdzeniem przeciwEOF
, ponieważ ma wartość-1
._ctype+1
jest arytmetyką wskaźnika, aby uzyskać adres elementu tablicy.[(unsigned char)_c]
jest po prostu dostępem do tablicy tej tablicy, gdzie rzutowanie ma na celu wymuszenie standardowego wymogu reprezentacji parametru jakounsigned char
. Zauważ, żechar
faktycznie może mieć wartość ujemną, więc jest to programowanie defensywne. Wynikiem[]
dostępu do tablicy jest pojedynczy znak z wewnętrznej tablicy symboli.&
Maskowanie jest tam, aby uzyskać pewną grupę znaków z tablicy symboli. Najwyraźniej wszystkie znaki z ustawionym bitem 5 (maska 0x20) są znakami kontrolnymi. Nie ma sensu tego bez oglądania stołu.źródło
unsigned char
. Standard wymaga, aby wartość * była reprezentowana jakounsigned char
lub równaEOF
, gdy wywoływana jest procedura. Rzutowanie służy jedynie jako „defensywne” programowanie: Korygowanie błędu programisty, który przechodzi znakchar
(lub asigned char
), kiedy na nich ciążył, aby przekazaćunsigned char
wartość przy użyciuctype.h
makra. Należy zauważyć, że nie może to poprawić błędu, gdychar
wartość -1 jest przekazywana w implementacji, która używa -1EOF
.+ 1
. Jeśli makro nie zawierało wcześniej tej korekty obronnej, mogło być zaimplementowane jedynie jako((_ctype_+1)[_c] & _C)
, mając w ten sposób tabelę indeksowaną wartościami wstępnej korekty od -1 do 255. Tak więc pierwszy wpis nie został pominięty i służył celowi. Gdy ktoś później dodał rzut obronny,EOF
wartość -1 nie działałaby z tym rzutem, więc dodali operator warunkowy, aby potraktować go specjalnie._ctype_
jest wskaźnikiem do globalnej tablicy 257 bajtów. Nie wiem do czego_ctype_[0]
służy._ctype_[1]
przez_ctype_[256]_
reprezentuje kategorie znaków odpowiednio 0,…, 255:_ctype_[c + 1]
reprezentuje kategorię znakuc
. Jest to to samo, co powiedzenie, że_ctype_ + 1
wskazuje na tablicę 256 znaków, gdzie(_ctype_ + 1)[c]
reprezentuje kategorię znakuc
.(_ctype_ + 1)[(unsigned char)_c]
nie jest deklaracją. Jest to wyrażenie wykorzystujące operator indeksu tablicy. Uzyskuje dostęp do pozycji(unsigned char)_c
tablicy, która zaczyna się od(_ctype_ + 1)
.Kod rzutujący
_c
zint
naunsigned char
nie jest absolutnie konieczny: funkcje ctype przyjmują wartości char rzutowane naunsigned char
(char
jest podpisane na OpenBSD): poprawne wywołanie tochar c; … iscntrl((unsigned char)c)
. Mają tę zaletę, że gwarantują, że nie nastąpi przepełnienie bufora: jeśli aplikacja wywołaiscntrl
wartość, która jest poza zakresemunsigned char
i nie jest -1, funkcja zwraca wartość, która może nie mieć znaczenia, ale przynajmniej nie spowoduje awaria lub wyciek prywatnych danych, które znalazły się pod adresem poza granicami tablicy. Wartość jest nawet poprawna, jeśli funkcja jest wywoływana,char c; … iscntrl(c)
dopókic
nie jest -1.Powodem specjalnego przypadku z -1 jest to, że tak jest
EOF
. Wiele standardowych funkcji C, które działają nachar
przykładgetchar
, reprezentują znak jakoint
wartość, która jest wartością char zawiniętą w dodatni zakres i używają specjalnej wartości,EOF == -1
aby wskazać, że żaden znak nie może zostać odczytany. Dla funkcji, takich jakgetchar
,EOF
wskazuje koniec pliku, stąd nazwa e nd- O F F Ile. Eric Postpischil sugeruje, że kod był pierwotnie sprawiedliwyreturn _ctype_[_c + 1]
i prawdopodobnie ma rację:_ctype_[0]
byłby wartością EOF. Ta prostsza implementacja powoduje przepełnienie bufora, jeśli funkcja jest niewłaściwie używana, podczas gdy bieżąca implementacja pozwala tego uniknąć, jak omówiono powyżej.Jeśli
v
wartość znajduje się w tablicy,v & _C
sprawdza, czy0x20
ustawiony jest bit atv
. Wartości w tablicy są maskami kategorii, w których znajduje się znak:_C
jest ustawiony dla znaków kontrolnych,_U
jest ustawiony na wielkie litery itp.źródło
(_ctype_ + 1)[_c]
użyłby poprawnego indeksu tablicowego określonego przez standard C, ponieważ to użytkownik jest odpowiedzialny za przekazanie jednejEOF
lubunsigned char
wartości. Zachowanie innych wartości nie jest zdefiniowane w standardzie C. Obsada nie służy do implementacji zachowania wymaganego przez standard C. Jest to obejście wprowadzone w celu ochrony przed błędami spowodowanymi przez programistów niepoprawnie przekazujących ujemne wartości znaków. Jednak jest niekompletny lub niepoprawny (i nie można go poprawić), ponieważ wartość -1 będzie koniecznie traktowana jakoEOF
.+ 1
. Jeśli makro nie zawierało wcześniej tej korekty obronnej, mogło być zaimplementowane jedynie jako((_ctype_+1)[_c] & _C)
, mając w ten sposób tabelę indeksowaną wartościami wstępnej korekty od -1 do 255. Tak więc pierwszy wpis nie został pominięty i służył celowi. Gdy ktoś później dodał rzut obronny,EOF
wartość -1 nie działałaby z tym rzutem, więc dodali operator warunkowy, aby potraktować go specjalnie.Zacznę od kroku 3:
Wskaźnik nie jest niezdefiniowany. Jest to zdefiniowane w innej jednostce kompilacyjnej. Tak
extern
mówi część kompilatorowi. Kiedy wszystkie pliki zostaną połączone, linker rozpozna odniesienia do niego.Więc na co to wskazuje?
Wskazuje tablicę z informacjami o każdym znaku. Każda postać ma swój własny wpis. Wpis jest bitmapową reprezentacją cech postaci. Na przykład: jeśli ustawiony jest bit 5, oznacza to, że znak jest znakiem kontrolnym. Kolejny przykład: jeśli ustawiony jest bit 0, oznacza to, że znak jest znakiem wyższym.
Więc coś takiego
(_ctype_ + 1)['x']
uzyska cechy, które dotyczą'x'
. Następnie wykonywana jest bitowa i sprawdzana, czy bit 5 jest ustawiony, tzn. Sprawdza, czy jest to znak kontrolny.Przyczyną dodania 1 jest prawdopodobnie to, że rzeczywisty indeks 0 jest zarezerwowany do jakiegoś specjalnego celu.
źródło
Wszystkie informacje tutaj oparte są na analizie kodu źródłowego (i doświadczenia w programowaniu).
Deklaracja
informuje kompilator, że istnieje wskaźnik do
const char
miejsca o nazwie_ctype_
.(4) Ten wskaźnik jest dostępny jako tablica.
Rzutowanie
(unsigned char)_c
zapewnia, że wartość indeksu mieści się w zakresieunsigned char
(0..255).Arytmetyka wskaźnika
_ctype_ + 1
skutecznie przesuwa pozycję tablicy o 1 element. Nie wiem, dlaczego zaimplementowali tablicę w ten sposób. Korzystanie z zakresu_ctype_[1]
.._ctype[256]
dla wartości znaków0
..255
pozostawia wartość_ctype_[0]
nieużywaną dla tej funkcji. (Przesunięcie 1 można zaimplementować na kilka alternatywnych sposobów.)Dostęp do tablicy pobiera wartość (typu
char
, aby zaoszczędzić miejsce), używając wartości znaku jako indeksu tablicy.(5) Bitowa operacja AND wyodrębnia jeden bit z wartości.
Najwyraźniej wartość z tablicy jest używana jako pole bitowe, w którym bit 5 (licząc od 0 rozpoczynając co najmniej znaczący bit =
0x20
) jest flagą dla „jest znakiem kontrolnym”. Tak więc tablica zawiera wartości pól bitowych opisujące właściwości znaków.źródło
+ 1
wskaźnik do wskaźnika, aby wyjaśnić, że1..256
zamiast tego uzyskują dostęp do elementów1..255,0
._ctype_[1 + (unsigned char)_c]
byłby równoważny z powodu niejawnej konwersji naint
. I_ctype_[(_c & 0xff) + 1]
byłoby jeszcze bardziej jasne i zwięzłe.Kluczem tutaj jest zrozumienie, co
(_ctype_ + 1)[(unsigned char)_c]
robi wyrażenie (które jest następnie podawane do bitowego i operacji,& 0x20
aby uzyskać wynik!Krótka odpowiedź: zwraca element
_c + 1
tablicy wskazany przez_ctype_
.W jaki sposób?
Po pierwsze, chociaż wydaje ci się, że uważasz, że
_ctype_
jest niezdefiniowany, tak naprawdę nie jest! Nagłówek deklaruje go jako zmienną zewnętrzną - ale jest zdefiniowany w (prawie na pewno) jednej z bibliotek wykonawczych, z którymi program jest powiązany podczas jego tworzenia.Aby zilustrować, w jaki sposób składnia odpowiada indeksowaniu tablic, spróbuj przepracować (a nawet skompilować) następujący krótki program:
Poproś o dodatkowe wyjaśnienia i / lub wyjaśnienia.
źródło
Funkcje zadeklarowane w
ctype.h
obiektach typu acceptint
. W przypadku znaków używanych jako argumenty zakłada się, że są one wstępnie rzutowane na typunsigned char
. Ten znak jest używany jako indeks w tabeli, która określa jego charakterystykę.Wygląda na to, że kontrola
_c == -1
jest używana w przypadku, gdy_c
zawiera wartośćEOF
. Jeśli nie,EOF
_c jest rzutowany na typ bez znaku, który jest używany jako indeks w tabeli wskazywanej przez wyrażenie_ctype_ + 1
. A jeśli bit określony przez maskę0x20
jest ustawiony, to znak jest symbolem kontrolnym.Aby zrozumieć wyrażenie
weź pod uwagę, że indeksowanie tablicy jest operatorem postfiksowym, który jest zdefiniowany jak
Nie możesz tak pisać
ponieważ to wyrażenie jest równoważne z
Tak więc wyrażenie
_ctype_ + 1
jest ujęte w nawiasy, aby uzyskać wyrażenie podstawowe.Tak naprawdę masz
który daje obiekt tablicy o indeksie, który jest obliczany jako wyrażenie, w
integral_expression
którym znajduje się wskaźnik(_ctype_ + 1)
(gere jest używany jako wskaźnik arytmetuc) iintegral_expression
który jest indeksem jest wyrażeniem(unsigned char)_c
.źródło