guia - ¿Cómo hacer una representación de bits de una manera C estándar?
qgis español (3)
En general, no es tan difícil acomodar plataformas inusuales en la mayoría de los casos (si no quiere simplemente asumir 8 bits bit, complemento a 2, sin relleno, sin trampa y truncando la conversión sin firmar), la La norma en su mayoría brinda suficientes garantías (sin embargo, algunas macros para inspeccionar ciertos detalles de implementación serían útiles).
Por lo que puede observar un programa estrictamente conforme (fuera de los campos de bits), 5 siempre se codifica como 00...0101
. Esta no es necesariamente la representación física (lo que sea que deba significar), sino lo que se puede observar mediante un código portátil. Una máquina que usa internamente el código Gray, por ejemplo, tendría que emular una "notación binaria pura" para los operadores y desplazamientos a nivel de bits.
Para valores negativos de tipos con signo, se permiten diferentes codificaciones, lo que conduce a resultados diferentes (pero bien definidos para cada caso) al reinterpretarse como el tipo sin signo correspondiente. Por ejemplo, el código estrictamente conforme debe distinguir entre (unsigned)n
y *(unsigned *)&n
para un entero con signo n
: Son iguales para el complemento de dos sin bits de relleno, pero diferentes para las otras codificaciones si n
es negativo.
Además, los bits de relleno pueden existir, y los tipos enteros con signo pueden tener más bits de relleno que sus homólogos no firmados correspondientes (pero no al revés, el punning de tipo de firmado a unsigned siempre es válido). No se puede usar sizeof
para obtener el número de bits sin relleno, así que, por ejemplo, para obtener un valor sin signo donde solo se establece el bit de signo (del tipo con signo correspondiente), se debe usar algo como esto:
#define TYPE_PUN(to, from, x) ( *(to *)&(from){(x)} )
unsigned sign_bit = TYPE_PUN(unsigned, int, INT_MIN) &
TYPE_PUN(unsigned, int, -1) & ~1u;
(probablemente hay formas más agradables) en lugar de
unsigned sign_bit = 1u << sizeof sign_bit * CHAR_BIT - 1;
ya que esto puede variar más que el ancho. (No conozco una expresión constante que sign_bit
el ancho, pero sign_bit
desde arriba se puede desplazar a la derecha hasta que sea 0 para determinarlo, Gcc puede plegar constantemente esa cantidad). Los bits de relleno se pueden inspeccionar memcpy
un unsigned char
matriz, aunque pueden parecer "oscilar": leer el mismo bit de relleno dos veces puede dar resultados diferentes.
Si desea el patrón de bits (sin relleno de bits) de un entero con signo (little endian):
int print_bits_u(unsigned n) {
for(; n; n>>=1) {
putchar(n&1 ? ''1'' : ''0''); // n&1 never traps
}
return 0;
}
int print_bits(int n) {
return print_bits_u(*(unsigned *)&n & INT_MAX);
/* This masks padding bits if int has more of them than unsigned int.
* Note that INT_MAX is promoted to unsigned int here. */
}
int print_bits_2scomp(int n) {
return print_bits_u(n);
}
print_bits
da diferentes resultados para números negativos dependiendo de la representación utilizada (da el patrón de bits en bruto), print_bits_2scomp
proporciona la representación del complemento de los dos (posiblemente con un ancho mayor al que tiene un signed int
, si el unsigned int
tiene menos bits de relleno).
Se debe tener cuidado de no generar representaciones de trampas cuando se usan operadores bitwise y cuando se tipifica el tipo desde sin signo a firmado, vea a continuación cómo se pueden generar potencialmente (como ejemplo, *(int *)&sign_bit
puede *(int *)&sign_bit
con el complemento de dos, y -1 | 1
puede atrapar con el complemento de unos).
La conversión de enteros sin signo a signo (si el valor convertido no es representable en el tipo de destino) siempre está definida por la implementación, esperaría que las máquinas del complemento no 2 difieran de la definición común, aunque técnicamente, también podría Conviértete en un problema en las implementaciones del complemento 2.
Desde C11 (n1570) 6.2.6.2:
(1) Para los tipos de enteros sin signo distintos de los caracteres
unsigned char
, los bits de la representación del objeto se dividirán en dos grupos: bits de valor y bits de relleno (no es necesario que haya ninguno de estos últimos). Si hay N bits de valor, cada bit representará una potencia diferente de 2 entre 1 y 2 N-1 , de modo que los objetos de ese tipo serán capaces de representar valores de 0 a 2 N -1 utilizando una representación binaria pura; Esto se conocerá como la representación del valor. Los valores de cualquier bit de relleno no están especificados.(2) Para los tipos de enteros con signo, los bits de la representación del objeto se dividirán en tres grupos: bits de valor, bits de relleno y el bit de signo. No es necesario que haya bits de relleno;
signed char
no tendrá bits de relleno. Debe haber exactamente un bit de signo. Cada bit que sea un bit de valor tendrá el mismo valor que el mismo bit en la representación del objeto del tipo sin signo correspondiente (si hay bits de valor M en el tipo con signo y N en el tipo sin signo, entonces M≤N ). Si el bit de signo es cero, no afectará el valor resultante. Si el bit de signo es uno, el valor se modificará de una de las siguientes maneras:
- el valor correspondiente con el bit de signo 0 se anula ( signo y magnitud );
- el bit de signo tiene el valor - (2 M ) ( complemento de dos );
- el bit de signo tiene el valor - (2 M -1) ( complemento de unos ).
Cuál de estos se aplica a la implementación, como si el valor con el bit de signo 1 y todos los bits de valor cero (para los dos primeros), o con el bit de signo y todos los bits de valor 1 (para el complemento de unos), es una representación de trampa o un valor normal. En el caso de signo y magnitud y el complemento de unos, si esta representación es un valor normal, se llama cero negativo.
Según el estándar de C, la representación del valor de un tipo entero se define por implementación. Por lo tanto, 5
podría no estar representado como 00000000000000000000000000000101
o -1
como 11111111111111111111111111111111
como suponemos en un complemento de 32 bits 2. Entonces, aunque los operadores ~
, <<
y >>
están bien definidos, los patrones de bits en los que trabajarán están definidos por la implementación. El único patrón de bits definido que pude encontrar fue "§5.2.1 / 3 Un byte con todos los bits establecidos en 0, llamado el carácter nulo, debe existir en el conjunto de caracteres de ejecución básica; se utiliza para terminar una cadena de caracteres". .
Entonces, mi pregunta es: ¿Existe una forma de implementación independiente de convertir tipos enteros en un patrón de bits?
Siempre podemos comenzar con un carácter nulo y hacer suficientes operaciones de bits en él para obtener el valor deseado, pero me parece demasiado engorroso. También me doy cuenta de que prácticamente todas las implementaciones usarán una representación de complemento a 2, pero quiero saber cómo hacerlo de una manera estándar en C pura. Personalmente, encuentro este tema bastante intrigante debido a la cuestión de la programación del controlador del dispositivo donde todo el código escrito hasta la fecha asume una implementación particular.
Para agregar a la excelente respuesta de mafso, hay una parte de la justificación de ANSI C que habla de esto:
El Comité ha restringido explícitamente el lenguaje C a las arquitecturas binarias, debido a que esta restricción estaba implícita en cualquier caso:
- Los campos de bits se especifican mediante un número de bits, sin mención de la representación de "entero no válido". La única codificación razonable para tales campos de bits es binaria.
- Los formatos de enteros para printf no sugieren provisiones para valores de "enteros no válidos", lo que implica que cualquier resultado de la manipulación a nivel de bits produce un resultado entero que puede ser impreso por printf.
- Todos los métodos para especificar constantes enteras (decimal, hexadecimal y octal) especifican un valor entero. No se define ningún método independiente de los enteros para especificar "constantes de cadena de bits". Solo una codificación binaria proporciona una asignación uno a uno completa entre cadenas de bits y valores enteros.
La restricción a los sistemas de numeración binaria descarta curiosidades tales como el código Gray y hace posible las definiciones aritméticas de los operadores bitwise en tipos sin signo.
La parte relevante de la norma podría ser esta cita:
3.1.2.5 Tipos
[...]
El tipo char, los tipos enteros con signo y sin signo, y los tipos enumerados se denominan colectivamente tipos integrales. Las representaciones de tipos integrales definirán valores mediante el uso de un sistema de numeración binaria pura.
Si desea obtener el patrón de bits de un int
dado, entonces los operadores de bits son sus amigos. Si desea convertir un int
en su representación de 2 complementos, los operadores aritméticos son sus amigos. Las dos representaciones pueden ser diferentes, como se define en la implementación:
Std Draft 2011. 6.5 / 4. Se requiere que algunos operadores (el operador unario ~, y los operadores binarios <<, >>, &, ^, y |, descritos colectivamente como operadores bitwise) tengan operandos que tengan tipo entero. Estos operadores producen valores que dependen de las representaciones internas de los enteros y tienen aspectos definidos por la implementación y no definidos para los tipos firmados.
Esto significa que i<<1
desplazará efectivamente el patrón de bits una posición hacia la izquierda, pero que el valor producido puede ser diferente de i*2
(incluso para valores pequeños de i
).