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:
- Se llama a iscntrl ('a') y 'a' se convierte a su valor entero
- primero verifique si _c es -1 y luego devuelve 0 más ...
- incremente la dirección a la que apunta el puntero indefinido en 1
- declare esta dirección como un puntero a una matriz de longitud (unsigned char) ((int) 'a')
- 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.
_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 que0x20
es 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 funcioneEOF
incluso sin la prueba adicional.)(unsigned char)
es cuidar la posibilidad de que los personajes estén firmados y sean negativos.Respuestas:
_ctype_
parece ser una versión interna restringida de la tabla de símbolos y supongo+ 1
que es que no se molestaron en guardar el índice0
ya 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:
Revisando el código paso a paso:
int iscntrl(int _c)
Losint
tipos son realmente caracteres, pero todas las funciones ctype.h son necesarias para manejarEOF
, por lo que deben serloint
.-1
es una verificación contraEOF
, 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 comounsigned char
. Tenga en cuenta que enchar
realidad 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.&
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.fuente
unsigned char
. El estándar requiere que el valor ya * sea representable comounsigned char
, o igualEOF
, cuando se llama a la rutina. El elenco solo sirve como programación "defensiva": corrige el error de un programador que pasa un signochar
(o asigned char
) cuando era responsabilidad de ellos pasar ununsigned char
valor al usar unactype.h
macro. Cabe señalar que esto no puede corregir el error cuandochar
se pasa un valor de -1 en una implementación que utiliza -1 paraEOF
.+ 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, elEOF
valor de -1 no funcionaría con ese lanzamiento, por lo que agregaron el operador condicional para tratarlo especialmente._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ácterc
. Esto es lo mismo que decir que_ctype_ + 1
apunta a una matriz de 256 caracteres donde(_ctype_ + 1)[c]
representa la categoría del personajec
.(_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)_c
de la matriz que comienza en(_ctype_ + 1)
.El código se convierte
_c
deint
aunsigned char
no es estrictamente necesario: las funciones ctype toman valores de caracteres emitidos aunsigned char
(char
está firmado en OpenBSD): una llamada correcta eschar c; … iscntrl((unsigned char)c)
. Tienen la ventaja de garantizar que no hay desbordamiento del búfer: si la aplicación llamaiscntrl
con un valor que está fuera del rango deunsigned char
y 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ónchar c; … iscntrl(c)
siempre quec
no sea -1.La razón del caso especial con -1 es que lo es
EOF
. Muchas funciones C estándar que operan en unchar
, por ejemplogetchar
, representan el carácter como unint
valor que es el valor de carácter envuelto en un rango positivo, y usan el valor especialEOF == -1
para indicar que no se puede leer ningún carácter. Para funciones comogetchar
,EOF
indica el final del archivo, de ahí el nombre e nd- o f- f ile. Eric Postpischil sugiere que el código era originalmente justoreturn _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
v
es el valor encontrado en la matriz,v & _C
prueba si el bit en0x20
está establecidov
. Los valores en la matriz son máscaras de las categorías en las que se encuentra el carácter:_C
está configurado para caracteres de control,_U
está configurado para letras mayúsculas, etc.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 seaEOF
o ununsigned char
valor. 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 comoEOF
.+ 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, elEOF
valor de -1 no funcionaría con ese lanzamiento, por lo que agregaron el operador condicional para tratarlo especialmente.Comenzaré con el paso 3:
El puntero no está indefinido. Simplemente se define en alguna otra unidad de compilación. Eso es lo que la
extern
parte 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.
fuente
Toda la información aquí se basa en el análisis del código fuente (y la experiencia de programación).
La declaracion
le dice al compilador que hay un puntero a un
const char
lugar llamado_ctype_
.(4) Se accede a este puntero como una matriz.
La conversión
(unsigned char)_c
se asegura de que el valor del índice esté en el rango de ununsigned char
(0..255).La aritmética del puntero
_ctype_ + 1
desplaza 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 caracteres0
...255
deja 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.fuente
+ 1
puntero para dejar en claro que están accediendo a elementos en1..256
lugar de1..255,0
._ctype_[1 + (unsigned char)_c]
habría sido equivalente debido a la conversión implícita aint
. Y_ctype_[(_c & 0xff) + 1]
habría sido aún más claro y conciso.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, ¡& 0x20
para obtener el resultado!Respuesta corta: devuelve el elemento
_c + 1
de 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:
No dude en solicitar más aclaraciones y / o explicaciones.
fuente
Las funciones declaradas en
ctype.h
aceptar objetos del tipoint
. 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 == -1
se usa en caso de que_c
contenga el valor deEOF
. 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
tenga en cuenta que el subscripting de matriz es un operador postfix que se define como
No puedes escribir como
porque esta expresión es equivalente a
Entonces la expresión
_ctype_ + 1
está encerrada entre paréntesis para obtener una expresión primaria.De hecho, tienes
que produce el objeto de una matriz en el índice que se calcula como la expresión
integral_expression
donde está el puntero(_ctype_ + 1)
(gere se usa el puntero arithmetuc) yintegral_expression
ese es el índice es la expresión(unsigned char)_c
.fuente