¿Cómo hacer una representación de bits de una manera C-estándar?


Según el estándar C, la representación del valor de un tipo entero está definida por la implementación. Así que 5 podría no ser representado como 00000000000000000000000000000101 o -1 como 11111111111111111111111111111111 como solemos suponer en un complemento de 2 de 32 bits. Así que aunque los operadores ~, << y >> están bien definidos, los patrones de bits en los que trabajarán están definidos en 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, existirá en la ejecución básica character set; se usa para terminar una cadena de caracteres.".

Así que mis preguntas son - ¿Hay una forma independiente de implementación de convertir tipos enteros a 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 de 2, pero quiero saber cómo hacerlo en un estándar C puro manera. Personalmente, este tema me parece bastante intrigante debido a la cuestión de la programación del controlador de dispositivo, donde todo el código escrito hasta la fecha asume una implementación particular.

Author: tinkerbeast, 2015-03-09

3 answers

En general, no es que sea difícil acomodar plataformas inusuales para la mayoría de los casos (si no desea simplemente asumir 8 bits char, complemento de 2, sin relleno, sin trampa y truncando la conversión sin signo a firmado), el estándar en su mayoría da suficientes garantías (algunas macros para inspeccionar ciertos detalles de implementación serían útiles, sin embargo).

En lo que un programa estrictamente conforme puede observar (fuera de los campos de bits), 5 siempre está codificado como 00...0101. Esto no es necesariamente la representación física (sea lo que sea que esto signifique), pero lo que es observable por código portable. Una máquina que usa código gris internamente, por ejemplo, tendría que emular una "notación binaria pura" para operadores y turnos de bits.

Para valores negativos de tipos con signo, se permiten codificaciones diferentes, lo que conduce a resultados diferentes (pero bien definidos para cada caso) cuando se reinterpreta 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 contrapartes sin signo correspondientes (pero no al revés, el juego de palabras de tipo de firmado a sin signo siempre es válido). sizeof no se puede usar para obtener el número de bits sin relleno, por ejemplo, para obtener un valor donde solo se establece el bit de signo (del tipo firmado 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 cambiar más que el ancho. (No conozco una expresión constante que dé el ancho, pero sign_bit desde arriba se puede desplazar a la derecha hasta que sea 0 para determinarlo, Gcc puede doblarlo constantemente.) Los bits de relleno pueden ser inspeccionados por memcpy ing en una matriz unsigned char, aunque pueden parecer " tambalearse": Leer el mismo bit de relleno dos veces puede dar resultados diferentes.

Si desea el patrón de bits (sin bits de relleno) 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 los números negativos dependiendo de la representación utilizada (da el patrón de bits sin procesar), print_bits_2scomp da la representación del complemento de los dos (posiblemente con un ancho mayor que el que tiene signed int, si unsigned int tiene menos bits de relleno).

Se debe tener cuidado de no generar trampa las representaciones cuando se usan operadores bit a bit y cuando se escribe de unsigned a signed, vea a continuación cómo se pueden generar potencialmente (como un ejemplo, *(int *)&sign_bit puede atrapar 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 de complemento no-2 difieran de la definición común más probablemente, sin embargo técnicamente, también podría convertirse en un problema en las implementaciones de complemento de 2.

De C11 (n1570) 6.2.6.2:

(1) Para tipos enteros sin signo distintos de 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 2N-1, para que los objetos de que tipo deberá ser capaz de representar valores de 0 to 2N-1 usando una representación binaria pura; esto se conocerá como la representación de valor. Los valores de los bits de relleno no están especificados.

(2) Para los tipos 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. Habrá exactamente uno firma poco. Cada bit que es 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 M bits de valor en el tipo y Nen el tipo sin signo, entonces M≤N). Si el bit de signo es cero, no afectará al 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 niega (signo y magnitud);
  • el bit de signo tiene el valor -(2M) (complemento a dos);
  • el bit de signo tiene el valor -(2M-1) (queridos complemento).

Cuál de estos se aplica está definido por la implementación, al igual que 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 valor. En el caso de signo y magnitud y complemento de unos, si esta representación es un valor normal se denomina cero negativo.

 19
Author: mafso,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-09 16:43:07

Para añadir a la excelente respuesta de mafso, hay una parte de la ANSI C rationale que habla de esto:

El Comité ha restringido explícitamente el lenguaje C a arquitecturas binarias, sobre la base de que esta restricción estaba implícita en cualquier caso:

  • Los campos de bits se especifican por un número de bits, sin mencionar la representación de "entero inválido". La única codificación razonable para tales campos de bits es binaria.
  • Los formatos enteros para printf no se sugiere ninguna provisión para valores "enteros no válidos", lo que implica que cualquier resultado de manipulación bit a bit 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 completa de uno a uno entre cadenas de bits y valores enteros.

La restricción a sistemas de numeración binaria descarta curiosidades tales como el código gris y hace posibles definiciones aritméticas de los operadores bitwise en tipos sin signo.

La parte relevante del estándar podría ser esta cita:

3.1.2.5 Tipos

[...]

El tipo char, los tipos enteros con y sin signo, y el los tipos enumerados se denominan colectivamente tipos integrales. El las representaciones de tipos integrales definirán valores mediante el uso de un sistema de numeración binaria.

 5
Author: benj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-09 15:06:19

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 a su representación de 2 complementos, entonces los operadores aritméticos son sus amigos. Las dos representaciones pueden ser diferentes, como se define la implementación:

Std Draft 2011. 6.5/4. Algunos operadores (el operador único~, y el operadores binarios>, &, ^, y/, colectivamente descritos como operadores bitwise) deben tener operandos que tienen entero tipo. Estos operadores producen valores que dependen de la representaciones de enteros, y han definido la implementación y aspectos indefinidos para tipos firmados.

Significa que i<<1 efectivamente cambiará el patrón de bits por una posición a la izquierda, pero que el valor producido puede ser diferente de i*2 (incluso para valores smal de i).

 1
Author: Jean-Baptiste Yunès,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-09 09:08:50