Quero entender o seguinte código:
//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}
Ele se origina do arquivo ctype.h do código-fonte do sistema operacional obenbsd. Esta função verifica se um caractere é um caractere de controle ou uma letra imprimível dentro do intervalo ascii. Esta é minha corrente de pensamento atual:
- iscntrl ('a') é chamado e 'a' é convertido em seu valor inteiro
- primeiro verifique se _c é -1 e retorne 0 mais ...
- incrementar o endereço apontado pelo ponteiro indefinido em 1
- declarar esse endereço como um ponteiro para uma matriz de comprimento (caracter não assinado) ((int) 'a')
- aplique o operador bit a bit e a _C (0x20) e a matriz (???)
De alguma forma, estranhamente, ele funciona e sempre que 0 é retornado, o caractere _c não é um caractere imprimível. Caso contrário, quando estiver imprimível, a função retornará um valor inteiro que não é de interesse especial. Meu problema de compreensão está nas etapas 3, 4 (um pouco) e 5.
Obrigado por qualquer ajuda.
_ctype_
é essencialmente uma matriz de máscaras de bits. Está sendo indexado pelo personagem de interesse. Assim_ctype_['A']
, conteria bits correspondentes a "alfa" e "maiúsculas",_ctype_['a']
conteria bits correspondentes a "alfa" e "minúsculas",_ctype_['1']
conteria um bit correspondente a "dígito", etc. Parece que0x20
é o bit correspondente a "controle" . Mas, por algum motivo, a_ctype_
matriz é deslocada em 1, de modo que os bits para'a'
estão realmente dentro_ctype_['a'+1]
. (Isso foi, provavelmente, para deixá-lo trabalhar paraEOF
, mesmo sem o teste extra.)(unsigned char)
é cuidar da possibilidade de os personagens serem assinados e negativos.Respostas:
_ctype_
parece ser uma versão interna restrita da tabela de símbolos e acho+ 1
que eles não se incomodaram em salvar o índice0
, pois esse não é imprimível. Ou, possivelmente, eles estão usando uma tabela indexada em 1 em vez de indexada em 0, como é personalizado em C.O padrão C determina isso para todas as funções ctype.h:
Percorrendo o código passo a passo:
int iscntrl(int _c)
Osint
tipos são realmente caracteres, mas todas as funções ctype.h são necessárias para lidarEOF
, portanto devem serint
.-1
é um cheque contraEOF
, pois tem o valor-1
._ctype+1
é aritmético do ponteiro para obter o endereço de um item da matriz.[(unsigned char)_c]
é simplesmente um acesso à matriz dessa matriz, em que a conversão existe para impor o requisito padrão do parâmetro que é representável comounsigned char
. Observe que,char
na verdade, pode conter um valor negativo, portanto é uma programação defensiva. O resultado do[]
acesso à matriz é um único caractere da tabela de símbolos interna.&
máscara está lá para obter um determinado grupo de caracteres da tabela de símbolos. Aparentemente, todos os caracteres com o bit 5 definido (máscara 0x20) são caracteres de controle. Não há sentido em entender isso sem ver a tabela.fonte
unsigned char
. O padrão exige que o valor já * seja representável comounsigned char
ou igualEOF
quando a rotina é chamada. O elenco serve apenas como programação “defensiva”: Corrigindo o erro de um programador que passa um sinalchar
(ou asigned char
) quando o ônus estava neles para passar umunsigned char
valor ao usar umactype.h
macro. Deve-se notar que isso não pode corrigir o erro quando umchar
valor de -1 é passado em uma implementação que usa -1 paraEOF
.+ 1
. Se a macro não contivesse esse ajuste defensivo anteriormente, ele poderia ter sido implementado apenas como((_ctype_+1)[_c] & _C)
, tendo uma tabela indexada com os valores de pré-ajuste -1 a 255. Portanto, a primeira entrada não foi ignorada e serviu a um propósito. Quando alguém mais tarde adicionava o elenco defensivo, oEOF
valor de -1 não funcionaria com esse elenco, então eles adicionavam o operador condicional para tratá-lo especialmente._ctype_
é um ponteiro para uma matriz global de 257 bytes. Não sei para que_ctype_[0]
é usado._ctype_[1]
through_ctype_[256]_
representa as categorias de caractere 0,…, 255 respectivamente:_ctype_[c + 1]
representa a categoria do caracterec
. É o mesmo que dizer que_ctype_ + 1
aponta para uma matriz de 256 caracteres, onde(_ctype_ + 1)[c]
representa a categoria do personagemc
.(_ctype_ + 1)[(unsigned char)_c]
não é uma declaração. É uma expressão usando o operador subscrito da matriz. Está acessando a posição(unsigned char)_c
da matriz que começa em(_ctype_ + 1)
.O código convertido
_c
deint
paraunsigned char
não é estritamente necessário: as funções ctype levam valores de char convertidos paraunsigned char
(char
é assinado no OpenBSD): uma chamada correta échar c; … iscntrl((unsigned char)c)
. Eles têm a vantagem de garantir que não haja excesso de buffer: se o aplicativo chamariscntrl
com um valor que esteja fora do intervalounsigned char
e não seja -1, essa função retornará um valor que pode não ser significativo, mas pelo menos não causará uma falha ou vazamento de dados particulares que estavam no endereço fora dos limites da matriz. O valor estará correto se a função for chamadachar c; … iscntrl(c)
contanto quec
não seja -1.A razão para o caso especial com -1 é que é
EOF
. Muitas funções C padrão que operam emchar
, por exemplogetchar
, representam o caractere como umint
valor que é o valor do caractere agrupado em um intervalo positivo e usam o valor especialEOF == -1
para indicar que nenhum caractere pode ser lido. Para funções comogetchar
,EOF
indica o final do arquivo, daí o nome e nd- o f- f ile. Eric Postpischil sugere que o código era originalmente justoreturn _ctype_[_c + 1]
, e isso provavelmente está correto:_ctype_[0]
seria o valor para o EOF. Essa implementação mais simples gera um estouro de buffer se a função for mal utilizada, enquanto a implementação atual evita isso conforme discutido acima.Se
v
é o valor encontrado na matriz,v & _C
testa se o bit em0x20
está definidov
. Os valores na matriz são máscaras das categorias em que o caractere está:_C
está definido para caracteres de controle,_U
está definido para letras maiúsculas, etc.fonte
(_ctype_ + 1)[_c]
iria utilizar o índice de matriz correcta, tal como especificado pelo padrão C, porque é da responsabilidade do utilizador para passar qualquerEOF
ou umunsigned char
valor. O comportamento para outros valores não é definido pelo padrão C. O elenco não serve para implementar o comportamento exigido pelo padrão C. É uma solução alternativa usada para se proteger contra erros causados por programadores que passam incorretamente valores negativos de caracteres. No entanto, ele está incompleto ou incorreto (e não pode ser corrigido) porque um valor de -1 caracteres será necessariamente tratado comoEOF
.+ 1
. Se a macro não contivesse esse ajuste defensivo anteriormente, ele poderia ter sido implementado apenas como((_ctype_+1)[_c] & _C)
, tendo uma tabela indexada com os valores de pré-ajuste -1 a 255. Portanto, a primeira entrada não foi ignorada e serviu a um propósito. Quando alguém mais tarde adicionava o elenco defensivo, oEOF
valor de -1 não funcionaria com esse elenco, então eles adicionavam o operador condicional para tratá-lo especialmente.Vou começar com o passo 3:
O ponteiro não está indefinido. É apenas definido em alguma outra unidade de compilação. Isso é o que a
extern
parte diz ao compilador. Portanto, quando todos os arquivos estiverem vinculados, o vinculador resolverá as referências a ele.Então, o que isso aponta?
Aponta para uma matriz com informações sobre cada caractere. Cada personagem tem sua própria entrada. Uma entrada é uma representação de bitmap de características para o caractere. Por exemplo: Se o bit 5 estiver definido, significa que o caractere é um caractere de controle. Outro exemplo: se o bit 0 estiver definido, significa que o caractere é um caractere superior.
Então, algo como
(_ctype_ + 1)['x']
obterá as características aplicáveis'x'
. Em seguida, um bit a bit é executado para verificar se o bit 5 está definido, ou seja, verificar se é um caractere de controle.A razão para adicionar 1 é provavelmente que o índice real 0 está reservado para algum propósito especial.
fonte
Todas as informações aqui são baseadas na análise do código fonte (e na experiência de programação).
A declaração
informa ao compilador que existe um ponteiro para
const char
algum lugar chamado_ctype_
.(4) Este ponteiro é acessado como uma matriz.
A conversão
(unsigned char)_c
assegura que o valor do índice esteja no intervalo de umunsigned char
(0..255).A aritmética do ponteiro
_ctype_ + 1
efetivamente altera a posição da matriz em 1 elemento. Não sei por que eles implementaram a matriz dessa maneira. Usando o intervalo_ctype_[1]
.._ctype[256]
para os valores dos caracteres0
..255
deixa o valor_ctype_[0]
não utilizado para esta função. (O deslocamento de 1 pode ser implementado de várias maneiras alternativas.)O acesso à matriz recupera um valor (do tipo
char
, para economizar espaço) usando o valor do caractere como índice da matriz.(5) A operação AND bit a bit extrai um único bit do valor.
Aparentemente, o valor da matriz é usado como um campo de bit em que o bit 5 (contando de 0 começando pelo menos significativo, =
0x20
) é um sinalizador para "é um caractere de controle". Portanto, a matriz contém valores de campo de bits que descrevem as propriedades dos caracteres.fonte
+ 1
para o ponteiro para deixar claro que eles estão acessando elementos em1..256
vez de1..255,0
._ctype_[1 + (unsigned char)_c]
teria sido equivalente devido à conversão implícita emint
. E_ctype_[(_c & 0xff) + 1]
teria sido ainda mais claro e conciso.A chave aqui é entender o que a expressão
(_ctype_ + 1)[(unsigned char)_c]
faz (que é alimentada no bit a bit e operação,& 0x20
para obter o resultado!Resposta curta: Retorna o elemento
_c + 1
da matriz apontada por_ctype_
.Quão?
Primeiro, embora você pareça pensar que
_ctype_
é indefinido , na verdade não é! O cabeçalho o declara como uma variável externa - mas é definido (quase certamente) em uma das bibliotecas de tempo de execução às quais seu programa está vinculado quando você o cria.Para ilustrar como a sintaxe corresponde à indexação da matriz, tente trabalhar com (mesmo compilando) o seguinte programa curto:
Sinta-se livre para pedir mais esclarecimentos e / ou explicações.
fonte
As funções declaradas em
ctype.h
aceitam objetos do tipoint
. Para caracteres usados como argumentos, pressupõe-se que eles sejam convertidos preliminarmente para o tipounsigned char
. Esse caractere é usado como um índice em uma tabela que determina a característica do caractere.Parece que a verificação
_c == -1
é usada no caso em que_c
contém o valor deEOF
. Se não éEOF
, _c é convertido no tipo char não assinado que é usado como um índice na tabela apontada pela expressão_ctype_ + 1
. E se o bit especificado pela máscara0x20
estiver definido, o caractere será um símbolo de controle.Para entender a expressão
leve em consideração que a assinatura de array é um operador postfix definido como
Você não pode escrever como
porque essa expressão é equivalente a
Então a expressão
_ctype_ + 1
é colocada entre parênteses para obter uma expressão primária.Então, na verdade você tem
que gera o objeto de uma matriz no índice que é calculado como a expressão
integral_expression
onde está o ponteiro(_ctype_ + 1)
(gere é usado o ponteiro arithmetuc) eintegral_expression
que é o índice é a expressão(unsigned char)_c
.fonte