Puntero C a la declaración de matriz con bit a bit y operador

9

Quiero entender el siguiente 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));
}

Se origina en el archivo ctype.h del código fuente del sistema operativo obenbsd. Esta función verifica si un carácter es un carácter de control o una letra imprimible dentro del rango ascii. Esta es mi cadena de pensamiento actual:

  1. Se llama a iscntrl ('a') y 'a' se convierte a su valor entero
  2. primero verifique si _c es -1 y luego devuelve 0 más ...
  3. incremente la dirección a la que apunta el puntero indefinido en 1
  4. declare esta dirección como un puntero a una matriz de longitud (unsigned char) ((int) 'a')
  5. aplicar el operador y el bit a _C (0x20) y la matriz (???)

De alguna manera, extrañamente, funciona y cada vez que se devuelve 0 el carácter dado _c no es un carácter imprimible. De lo contrario, cuando se puede imprimir, la función solo devuelve un valor entero que no tiene ningún interés especial. Mi problema de comprensión está en los pasos 3, 4 (un poco) y 5.

Gracias por cualquier ayuda.

acento
fuente
1
_ctype_es esencialmente un conjunto de máscaras de bits. Está siendo indexado por el personaje de interés. Por _ctype_['A']lo tanto , contendría bits correspondientes a "alfa" y "mayúsculas", _ctype_['a']contendría bits correspondientes a "alfa" y "minúsculas", _ctype_['1']contendría un bit correspondiente a "dígito", etc. Parece que 0x20es el bit correspondiente a "control" . Pero por alguna razón, la _ctype_matriz está compensada por 1, por lo que los bits para 'a'están realmente en _ctype_['a'+1]. (Eso fue probablemente para que funcione EOFincluso sin la prueba adicional.)
Steve Summit
El reparto (unsigned char)es cuidar la posibilidad de que los personajes estén firmados y sean negativos.
Steve Summit

Respuestas:

3

_ctype_parece ser una versión interna restringida de la tabla de símbolos y supongo + 1que es que no se molestaron en guardar el índice 0ya que esa no es imprimible. O posiblemente estén usando una tabla indexada en 1 en lugar de 0 indexada como es costumbre en C.

El estándar C dicta esto para todas las funciones ctype.h:

En todos los casos, el argumento es un int, cuyo valor será representable como unsigned charo será igual al valor de la macroEOF

Revisando el código paso a paso:

  • int iscntrl(int _c)Los inttipos son realmente caracteres, pero todas las funciones ctype.h son necesarias para manejar EOF, por lo que deben serlo int.
  • La verificación contra -1es una verificación contra EOF, ya que tiene el valor -1.
  • _ctype+1 es una aritmética de puntero para obtener una dirección de un elemento de matriz.
  • [(unsigned char)_c]es simplemente un acceso a la matriz de esa matriz, donde la conversión está allí para hacer cumplir el requisito estándar de que el parámetro sea representable como unsigned char. Tenga en cuenta que en charrealidad puede tener un valor negativo, por lo que esta es una programación defensiva. El resultado del []acceso a la matriz es un solo carácter de su tabla de símbolos interna.
  • El &enmascaramiento está ahí para obtener un cierto grupo de caracteres de la tabla de símbolos. Aparentemente, todos los caracteres con el bit 5 establecido (máscara 0x20) son caracteres de control. No tiene sentido esto sin ver la mesa.
  • Cualquier cosa con el bit 5 establecido devolverá el valor enmascarado con 0x20, que es un valor distinto de cero. Esto satisface el requisito de que la función devuelva un valor distinto de cero en caso de un valor booleano verdadero.
Lundin
fuente
No es correcto que el modelo satisfaga el requisito estándar de que el valor sea representable como unsigned char. El estándar requiere que el valor ya * sea ​​representable como unsigned char, o igual EOF, cuando se llama a la rutina. El elenco solo sirve como programación "defensiva": corrige el error de un programador que pasa un signo char(o a signed char) cuando era responsabilidad de ellos pasar un unsigned charvalor al usar una ctype.hmacro. Cabe señalar que esto no puede corregir el error cuando charse pasa un valor de -1 en una implementación que utiliza -1 para EOF.
Eric Postpischil
Esto también ofrece una explicación de la + 1. Si la macro no contenía previamente este ajuste defensivo, entonces podría haberse implementado simplemente como ((_ctype_+1)[_c] & _C), teniendo así una tabla indexada con los valores de preajuste −1 a 255. Por lo tanto, la primera entrada no se omitió y sirvió para un propósito. Cuando alguien más tarde agregó el lanzamiento defensivo, el EOFvalor de -1 no funcionaría con ese lanzamiento, por lo que agregaron el operador condicional para tratarlo especialmente.
Eric Postpischil
3

_ctype_es un puntero a una matriz global de 257 bytes. No sé para qué _ctype_[0]se usa. _ctype_[1]a través de _ctype_[256]_representar las categorías de caracteres de los caracteres 0, ..., 255 respectivamente: _ctype_[c + 1]representa la categoría del carácter c. Esto es lo mismo que decir que _ctype_ + 1apunta a una matriz de 256 caracteres donde (_ctype_ + 1)[c]representa la categoría del personaje c.

(_ctype_ + 1)[(unsigned char)_c]No es una declaración. Es una expresión que usa el operador de subíndice de matriz. Está accediendo a la posición (unsigned char)_cde la matriz que comienza en (_ctype_ + 1).

El código se convierte _cde inta unsigned charno es estrictamente necesario: las funciones ctype toman valores de caracteres emitidos a unsigned char( charestá firmado en OpenBSD): una llamada correcta es char c; … iscntrl((unsigned char)c). Tienen la ventaja de garantizar que no hay desbordamiento del búfer: si la aplicación llama iscntrlcon un valor que está fuera del rango de unsigned chary no es -1, esta función devuelve un valor que puede no ser significativo pero al menos no causará un bloqueo o una fuga de datos privados que resultó estar en la dirección fuera de los límites de la matriz. El valor es incluso correcto si se llama a la función char c; … iscntrl(c)siempre que cno sea -1.

La razón del caso especial con -1 es que lo es EOF. Muchas funciones C estándar que operan en un char, por ejemplo getchar, representan el carácter como un intvalor que es el valor de carácter envuelto en un rango positivo, y usan el valor especial EOF == -1para indicar que no se puede leer ningún carácter. Para funciones como getchar, EOFindica el final del archivo, de ahí el nombre e nd- o f- f ile. Eric Postpischil sugiere que el código era originalmente justo return _ctype_[_c + 1], y probablemente sea correcto: _ctype_[0]sería el valor para EOF. Esta implementación más simple produce un desbordamiento del búfer si la función se usa incorrectamente, mientras que la implementación actual evita esto como se discutió anteriormente.

Si ves el valor encontrado en la matriz, v & _Cprueba si el bit en 0x20está establecido v. Los valores en la matriz son máscaras de las categorías en las que se encuentra el carácter: _Cestá configurado para caracteres de control, _Uestá configurado para letras mayúsculas, etc.

Gilles 'SO- deja de ser malvado'
fuente
(_ctype_ + 1)[_c] sería utilizar el índice de matriz correcta tal como se especifica por la norma C, ya que es la responsabilidad del usuario para pasar ya sea EOFo un unsigned charvalor. El comportamiento para otros valores no está definido por el estándar C. El elenco no sirve para implementar el comportamiento requerido por el estándar C. Es una solución alternativa para proteger contra errores causados ​​por programadores que pasan incorrectamente valores de caracteres negativos. Sin embargo, es incompleto o incorrecto (y no se puede corregir) porque un valor de carácter -1 se tratará necesariamente como EOF.
Eric Postpischil
Esto también ofrece una explicación de la + 1. Si la macro no contenía previamente este ajuste defensivo, entonces podría haberse implementado simplemente como ((_ctype_+1)[_c] & _C), teniendo así una tabla indexada con los valores de preajuste −1 a 255. Por lo tanto, la primera entrada no se omitió y sirvió para un propósito. Cuando alguien más tarde agregó el lanzamiento defensivo, el EOFvalor de -1 no funcionaría con ese lanzamiento, por lo que agregaron el operador condicional para tratarlo especialmente.
Eric Postpischil
2

Comenzaré con el paso 3:

incremente la dirección a la que apunta el puntero indefinido en 1

El puntero no está indefinido. Simplemente se define en alguna otra unidad de compilación. Eso es lo que la externparte le dice al compilador. Entonces, cuando todos los archivos están vinculados, el vinculador resolverá las referencias a él.

Entonces, ¿a qué apunta?

Apunta a una matriz con información sobre cada personaje. Cada personaje tiene su propia entrada. Una entrada es una representación de mapa de bits de características para el personaje. Por ejemplo: si se establece el bit 5, significa que el carácter es un carácter de control. Otro ejemplo: si se establece el bit 0, significa que el carácter es un carácter superior.

Entonces, algo así (_ctype_ + 1)['x']obtendrá las características que se aplican a 'x'. Luego se realiza un bit a bit para verificar si el bit 5 está configurado, es decir, verificar si es un carácter de control.

La razón para agregar 1 es probablemente que el índice real 0 está reservado para algún propósito especial.

4386427
fuente
1

Toda la información aquí se basa en el análisis del código fuente (y la experiencia de programación).

La declaracion

extern const char *_ctype_;

le dice al compilador que hay un puntero a un const charlugar llamado _ctype_.

(4) Se accede a este puntero como una matriz.

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

La conversión (unsigned char)_cse asegura de que el valor del índice esté en el rango de un unsigned char(0..255).

La aritmética del puntero _ctype_ + 1desplaza efectivamente la posición de la matriz en 1 elemento. No sé por qué implementaron la matriz de esta manera. El uso del rango _ctype_[1]... _ctype[256]para los valores de caracteres 0... 255deja el valor _ctype_[0]sin usar para esta función. (La compensación de 1 podría implementarse de varias maneras alternativas).

El acceso a la matriz recupera un valor (de tipo char, para ahorrar espacio) utilizando el valor de caracteres como índice de la matriz.

(5) La operación AND bit a bit extrae un solo bit del valor.

Aparentemente, el valor de la matriz se usa como un campo de bits donde el bit 5 (contando desde 0 comenzando al menos un bit significativo, = 0x20) es un indicador de "es un carácter de control". Por lo tanto, la matriz contiene valores de campo de bits que describen las propiedades de los caracteres.

Bodo
fuente
Supongo que movieron el + 1puntero para dejar en claro que están accediendo a elementos en 1..256lugar de 1..255,0. _ctype_[1 + (unsigned char)_c]habría sido equivalente debido a la conversión implícita a int. Y _ctype_[(_c & 0xff) + 1]habría sido aún más claro y conciso.
cmaster - reinstalar a monica el
0

La clave aquí es entender lo que hace la expresión (_ctype_ + 1)[(unsigned char)_c](que luego se alimenta al bit a bit y la operación, ¡ & 0x20para obtener el resultado!

Respuesta corta: devuelve el elemento _c + 1de la matriz señalado por _ctype_.

¿Cómo?

Primero, aunque parezca que no_ctype_ está definido, en realidad no lo es. El encabezado lo declara como una variable externa, pero se define (casi con certeza) en una de las bibliotecas de tiempo de ejecución con las que está vinculado su programa cuando lo construye.

Para ilustrar cómo la sintaxis corresponde a la indexación de matriz, intente trabajar (incluso compilar) el siguiente programa corto:

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

No dude en solicitar más aclaraciones y / o explicaciones.

Adrian Mole
fuente
0

Las funciones declaradas en ctype.haceptar objetos del tipo int. Para los caracteres utilizados como argumentos, se supone que son emitidos preliminarmente al tipounsigned char . Este carácter se usa como índice en una tabla que determina la característica del personaje.

Parece que la verificación _c == -1se usa en caso de que _ccontenga el valor de EOF. Si no es así EOF, _c se convierte al tipo unsigned char que se utiliza como índice en la tabla a la que apunta la expresión _ctype_ + 1. Y si el bit especificado por la máscara0x20 , entonces el carácter es un símbolo de control.

Para entender la expresión

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

tenga en cuenta que el subscripting de matriz es un operador postfix que se define como

postfix-expression [ expression ]

No puedes escribir como

_ctype_ + 1[(unsigned char)_c]

porque esta expresión es equivalente a

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

Entonces la expresión _ctype_ + 1está encerrada entre paréntesis para obtener una expresión primaria.

De hecho, tienes

pointer[integral_expression]

que produce el objeto de una matriz en el índice que se calcula como la expresión integral_expressiondonde está el puntero (_ctype_ + 1)(gere se usa el puntero arithmetuc) y integral_expressionese es el índice es la expresión (unsigned char)_c.

Vlad de Moscú
fuente