Ponteiro C para declaração de array com bit a bit e operador

9

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:

  1. iscntrl ('a') é chamado e 'a' é convertido em seu valor inteiro
  2. primeiro verifique se _c é -1 e retorne 0 mais ...
  3. incrementar o endereço apontado pelo ponteiro indefinido em 1
  4. declarar esse endereço como um ponteiro para uma matriz de comprimento (caracter não assinado) ((int) 'a')
  5. 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.

accentWool
fonte
11
_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 que 0x20é 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 para EOF, mesmo sem o teste extra.)
Steve Summit
O elenco (unsigned char)é cuidar da possibilidade de os personagens serem assinados e negativos.
Steve Summit

Respostas:

3

_ctype_parece ser uma versão interna restrita da tabela de símbolos e acho + 1que eles não se incomodaram em salvar o índice 0, 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:

Em todos os casos, o argumento é um int, cujo valor deve ser representável como unsigned charou deve ser igual ao valor da macroEOF

Percorrendo o código passo a passo:

  • int iscntrl(int _c)Os inttipos são realmente caracteres, mas todas as funções ctype.h são necessárias para lidar EOF, portanto devem ser int.
  • O cheque contra -1é um cheque contra EOF, 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 como unsigned char. Observe que, charna 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.
  • A &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.
  • Qualquer coisa com o bit 5 definido retornará o valor mascarado com 0x20, que é um valor diferente de zero. Isso satisfaz o requisito da função retornando diferente de zero no caso de boolean true.
Lundin
fonte
Não é correto que o elenco atenda ao requisito padrão de que o valor seja representável como unsigned char. O padrão exige que o valor já * seja representável como unsigned charou igual EOFquando a rotina é chamada. O elenco serve apenas como programação “defensiva”: Corrigindo o erro de um programador que passa um sinal char(ou a signed char) quando o ônus estava neles para passar um unsigned charvalor ao usar uma ctype.hmacro. Deve-se notar que isso não pode corrigir o erro quando um charvalor de -1 é passado em uma implementação que usa -1 para EOF.
Eric Postpischil
Isso também oferece uma explicação do + 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, o EOFvalor de -1 não funcionaria com esse elenco, então eles adicionavam o operador condicional para tratá-lo especialmente.
Eric Postpischil 15/11/19
3

_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 caractere c. É o mesmo que dizer que _ctype_ + 1aponta para uma matriz de 256 caracteres, onde (_ctype_ + 1)[c]representa a categoria do personagem c.

(_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)_cda matriz que começa em (_ctype_ + 1).

O código convertido _cde intpara unsigned charnão é estritamente necessário: as funções ctype levam valores de char convertidos para unsigned 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 chamar iscntrlcom um valor que esteja fora do intervalo unsigned chare 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 chamada char c; … iscntrl(c)contanto que cnão seja -1.

A razão para o caso especial com -1 é que é EOF. Muitas funções C padrão que operam em char, por exemplo getchar, representam o caractere como um intvalor que é o valor do caractere agrupado em um intervalo positivo e usam o valor especial EOF == -1para indicar que nenhum caractere pode ser lido. Para funções como getchar, EOFindica o final do arquivo, daí o nome e nd- o f- f ile. Eric Postpischil sugere que o código era originalmente justo return _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 & _Ctesta se o bit em 0x20está definido v. Os valores na matriz são máscaras das categorias em que o caractere está: _Cestá definido para caracteres de controle, _Uestá definido para letras maiúsculas, etc.

Gilles 'SO- parar de ser mau'
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 qualquer EOFou um unsigned charvalor. 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 como EOF.
Eric Postpischil
Isso também oferece uma explicação do + 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, o EOFvalor de -1 não funcionaria com esse elenco, então eles adicionavam o operador condicional para tratá-lo especialmente.
Eric Postpischil
2

Vou começar com o passo 3:

incrementar o endereço apontado pelo ponteiro indefinido em 1

O ponteiro não está indefinido. É apenas definido em alguma outra unidade de compilação. Isso é o que a externparte 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.

4386427
fonte
1

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

extern const char *_ctype_;

informa ao compilador que existe um ponteiro para const charalgum lugar chamado _ctype_.

(4) Este ponteiro é acessado como uma matriz.

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

A conversão (unsigned char)_cassegura que o valor do índice esteja no intervalo de um unsigned char(0..255).

A aritmética do ponteiro _ctype_ + 1efetivamente 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 caracteres 0.. 255deixa 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.

Bodo
fonte
Eu acho que eles moveram o + 1para o ponteiro para deixar claro que eles estão acessando elementos em 1..256vez de 1..255,0. _ctype_[1 + (unsigned char)_c]teria sido equivalente devido à conversão implícita em int. E _ctype_[(_c & 0xff) + 1]teria sido ainda mais claro e conciso.
cmaster - reinstate monica 15/11/19
0

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 + 1da 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:

#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;
}

Sinta-se livre para pedir mais esclarecimentos e / ou explicações.

Adrian Mole
fonte
0

As funções declaradas em ctype.haceitam objetos do tipo int. Para caracteres usados ​​como argumentos, pressupõe-se que eles sejam convertidos preliminarmente para o tipo unsigned 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 _ccontém o valor de EOF. 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

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

leve em consideração que a assinatura de array é um operador postfix definido como

postfix-expression [ expression ]

Você não pode escrever como

_ctype_ + 1[(unsigned char)_c]

porque essa expressão é equivalente a

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

Então a expressão _ctype_ + 1 é colocada entre parênteses para obter uma expressão primária.

Então, na verdade você tem

pointer[integral_expression]

que gera o objeto de uma matriz no índice que é calculado como a expressão integral_expressiononde está o ponteiro (_ctype_ + 1)(gere é usado o ponteiro arithmetuc) e integral_expressionque é o índice é a expressão (unsigned char)_c.

Vlad de Moscou
fonte