¿Por qué asignar un valor a un campo de bits no devuelve el mismo valor?

12 minutos de lectura

avatar de usuario
iammilind

Vi el siguiente código en esta publicación de Quora:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = 1;
  if(s.enabled == 1)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

Tanto en C como en C++, la salida del código es inesperado,

Está desactivado !!

Aunque la explicación relacionada con el “bit de signo” se proporciona en esa publicación, no puedo entender cómo es posible que establezcamos algo y luego no se refleje como es.

¿Alguien puede dar una explicación más elaborada?


Nota: Se requieren las etiquetas c y c++, porque sus estándares difieren ligeramente para describir los campos de bits. Consulte las respuestas para la especificación C y la especificación C++.

  • Dado que el campo de bits se declara como int creo que solo puede contener los valores 0 y -1.

    – Osiris

    19 de diciembre de 2018 a las 14:45

  • solo piense en cómo int almacena -1. Todos los bits están configurados en 1. Por lo tanto, si solo tiene un bit, claramente tiene que ser -1. Entonces 1 y -1 en el int de 1 bit son lo mismo. Cambie la verificación a ‘if (s.enabled! = 0)’ y funciona. Porque 0 no puede ser.

    – Jürgen

    19 de diciembre de 2018 a las 15:09

  • Es cierto que estas reglas son las mismas en C y C++. Pero de acuerdo con las políticas de uso de etiquetas, solo debemos etiquetar esto como C y abstenernos de etiquetar de forma cruzada cuando no sea necesario. Eliminaré la parte de C++, no debería afectar las respuestas publicadas.

    – Lundin

    19 de diciembre de 2018 a las 16:06

  • ¿Has probado a cambiarlo por struct mystruct { unsigned int enabled:1; };?

    – ChatterOne

    19 de diciembre de 2018 a las 16:08

  • Lea atentamente las políticas de etiquetas de C y C++, en particular la parte relacionada con el etiquetado cruzado de C y C++, establecidas a través del consenso de la comunidad aquí. No voy a entrar en una guerra de reversión, pero esta pregunta está etiquetada incorrectamente como C++. Incluso si los idiomas tienen alguna ligera diferencia debido a varios TC, haga una pregunta por separado sobre la diferencia entre C y C ++.

    – Lundin

    20 de diciembre de 2018 a las 8:04

avatar de usuario
Lundin

Los campos de bits están increíblemente mal definidos por el estándar. Dado este código struct mystruct {int enabled:1;};Entonces nosotros no saber:

  • Cuánto espacio ocupa esto, si hay bits/bytes de relleno y dónde están ubicados en la memoria.
  • Dónde se encuentra el bit en la memoria. No definido y también depende de la endianess.
  • Ya sea un int:n bitfield debe considerarse como firmado o sin firmar.

En cuanto a la última parte, C17 6.7.2.1/10 dice:

Se interpreta que un campo de bits tiene un tipo de entero con signo o sin signo que consiste en el número especificado de bits 125)

Nota no normativa que explica lo anterior:

125) Como se especifica en 6.7.2 anterior, si el especificador de tipo real utilizado es int o un typedef-name definido como intentonces está definido por la implementación si el campo de bits está firmado o no.

En caso de que el campo de bits se considere como signed int y haces un poco de tamaño 1, entonces no hay espacio para datos, solo para el bit de signo. Esta es la razón por la que su programa puede dar resultados extraños en algunos compiladores.

Buena práctica:

  • Nunca use campos de bits para ningún propósito.
  • Evite el uso de firmas int type para cualquier forma de manipulación de bits.

  • En el trabajo tenemos static_asserts sobre el tamaño y la dirección de los campos de bits solo para asegurarnos de que no se rellenen. Usamos campos de bits para registros de hardware en nuestro firmware.

    – Miguel

    19 de diciembre de 2018 a las 19:38

  • @Lundin: Lo feo con las máscaras y compensaciones #define-d es que su código se llena de cambios y operadores AND/OR bit a bit. Con los campos de bits, el compilador se encarga de eso por usted.

    – Miguel

    20 de diciembre de 2018 a las 8:25


  • @Miguel Con los campos de bits, el compilador se encarga de eso por usted. Bueno, está bien si sus estándares para “se ocupa de eso” son “no portátiles” e “impredecibles”. Los míos son más altos que eso.

    –Andrew Henle

    20 de diciembre de 2018 a las 10:56


  • @AndrewHenle Leushenko dice que desde la perspectiva de solo el estándar C en sídepende de la implementación si elige o no seguir la ABI x86-64 o no.

    – mtraceur

    20 de diciembre de 2018 a las 22:41

  • @AndrewHenle Correcto, estoy de acuerdo en ambos puntos. Mi punto fue que creo que su desacuerdo con Leushenko se reduce al hecho de que está usando “definición de implementación” para referirse solo a cosas que no están estrictamente definidas por el estándar C ni estrictamente definidas por la plataforma ABI, y él lo está usando para referirse a cualquier cosa que no esté estrictamente definida solo por el estándar C.

    – mtraceur

    20 dic 2018 a las 23:20


avatar de usuario
HostileFork dice que no confíes en SE

No logro entender, como es posible que configuremos algo y luego no aparezca como esta.

¿Estás preguntando por qué compila vs. te da un error?

Sí, idealmente debería darte un error. Y lo hace, si usa las advertencias de su compilador. En CCG, con -Werror -Wall -pedantic:

main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1' 
changes value from '1' to '-1' [-Werror=overflow]
   s.enabled = 1;
           ^

El razonamiento de por qué esto se deja definido por la implementación frente a un error puede tener más que ver con los usos históricos, donde requerir una conversión significaría romper el código antiguo. Los autores de la norma pueden creer que las advertencias fueron suficientes para compensar a los interesados.

Para agregar algo de prescriptivismo, haré eco de la declaración de @Lundin: “Nunca use campos de bits para ningún propósito”. Si tiene el tipo de buenas razones para obtener detalles de diseño de memoria de bajo nivel y específicos que lo llevarían a pensar que necesitaba campos de bits en primer lugar, los otros requisitos asociados que tiene casi seguramente se encontrarán con su subespecificación.

(TL; DR: si es lo suficientemente sofisticado como para “necesitar” campos de bits legítimamente, no están lo suficientemente bien definidos para servirle).

  • Los autores del estándar estaban de vacaciones el día que se diseñó el capítulo de campo de bits. Así que el conserje tuvo que hacerlo. No hay ninguna razón sobre cualquier cosa con respecto a cómo se diseñan los campos de bits.

    – Lundin

    19 dic 2018 a las 15:00


  • no hay coherencia técnico razón fundamental. Pero eso me lleva a concluir que hubo un político justificación: para evitar hacer que cualquiera de los códigos o implementaciones existentes sea incorrecto. Pero el resultado es que hay muy poco acerca de los campos de bits en los que pueda confiar.

    – John Bollinger

    19 dic 2018 a las 15:20

  • @JohnBollinger Definitivamente hubo políticas en su lugar, que causaron mucho daño a C90. Una vez hablé con un miembro del comité que me explicó el origen de mucha basura: no se podía permitir que la norma ISO favoreciera ciertas tecnologías existentes. Esta es la razón por la que estamos atascados con cosas tontas como el soporte para el complemento de 1 y la magnitud con signo, la firma definida por la implementación de charsoporte para bytes que no son de 8 bits, etc., etc. No se les permitió dar a las computadoras idiotas una desventaja en el mercado.

    – Lundin

    19 de diciembre de 2018 a las 15:26


  • @Lundin Sería interesante ver una colección de escritos y autopsias de personas que creían que las compensaciones se habían realizado por error y por qué. Me pregunto cuánto estudio de estos “hicimos eso la última vez, y funcionó/no funcionó” se ha convertido en conocimiento institucional para informar el próximo caso de este tipo, en lugar de solo historias en la cabeza de las personas.

    – HostileFork dice que no confíes en SE

    19 de diciembre de 2018 a las 15:29

  • Esto todavía se enumera como el punto no. 1 de los principios originales de C en la Carta C2x: “El código existente es importante, las implementaciones existentes no lo son”. … “ninguna implementación se mantuvo como el ejemplo por el cual definir C: Se supone que todas las implementaciones existentes deben cambiar un poco para cumplir con el Estándar”.

    – Leushenko

    19 dic 2018 a las 16:00

avatar de usuario
NathanOliver

Este es un comportamiento definido por la implementación. Estoy asumiendo que las máquinas en las que está ejecutando esto usan enteros con signo de complemento de dos y tratan int en este caso, como un entero con signo para explicar por qué no ingresa si es parte verdadera de la instrucción if.

struct mystruct { int enabled:1; };

declara enable como un campo de bits de 1 bit. Como está firmado, los valores válidos son -1 y 0. Configurando el campo para 1 se desborda ese bit volviendo a -1 (este es un comportamiento indefinido)

Esencialmente, cuando se trata de un campo de bits firmado, el valor máximo es 2^(bits - 1) - 1 cual es 0 en este caso.

  • “Desde que está firmado, los valores válidos son -1 y 0”. ¿Quién dijo que está firmado? No está definido, sino un comportamiento definido por la implementación. Si está firmado, entonces los valores válidos son - y +. El complemento a 2 no importa.

    – Lundin

    19 de diciembre de 2018 a las 14:56


  • @Lundin Un número de complemento de dos bits de 1 bit solo tiene dos valores posibles. Si el bit está establecido, dado que es el bit de signo, es -1. Si no está configurado, entonces es “positivo” 0. Sé que esto está definido por la implementación, solo estoy explicando los resultados usando la implantación más común

    – NathanOliver

    19 dic 2018 a las 15:00

  • La clave aquí es más bien que el complemento a 2 o cualquier otra forma firmada no puede funcionar con un solo bit disponible.

    – Lundin

    19 de diciembre de 2018 a las 15:03

  • @JohnBollinger Lo entiendo. Es por eso que tengo el descargo de que esta es una implementación definida. Al menos para los 3 grandes que todos tratan. int como se firma en este caso. Es una pena que los campos de bits estén tan poco especificados. Básicamente aquí está esta característica, consulte a su compilador sobre cómo usarla.

    – NathanOliver

    19 de diciembre de 2018 a las 15:08


  • @Lundin, la redacción del estándar para la representación de enteros con signo puede manejar perfectamente el caso en el que hay bits de valor cero, al menos en dos de las tres alternativas permitidas. Esto funciona porque asigna (negativo) valores de lugar para firmar bits, en lugar de darles una interpretación algorítmica.

    – John Bollinger

    19 de diciembre de 2018 a las 15:08


Se podría pensar que en el sistema de complemento a 2, el bit más a la izquierda es el bit de signo. Cualquier entero con signo con el conjunto de bits más a la izquierda es, por lo tanto, un valor negativo.

Si tiene un entero con signo de 1 bit, solo tiene el bit de signo. Así que asignando 1 a ese único bit solo se puede establecer el bit de signo. Entonces, al volver a leerlo, el valor se interpreta como negativo y también lo es -1.

Los valores que puede contener un entero de 1 bit con signo son -2^(n-1)= -2^(1-1)= -2^0= -1 y 2^n-1= 2^1-1=0

avatar de usuario
iammilind

según el C++ estándar n4713, se proporciona un fragmento de código muy similar. El tipo utilizado es BOOL (personalizado), pero se puede aplicar a cualquier tipo.

12.2.4

4 Si el valor verdadero o falso se almacena en un campo de bits de tipo bool de cualquier tamaño (incluido un campo de bits de un bit), el original bool el valor y el valor del campo de bits se compararán iguales. Si el valor de un enumerador se almacena en un campo de bits del mismo tipo de enumeración y el número de bits en el campo de bits es lo suficientemente grande como para contener todos los valores de ese tipo de enumeración (10.2), el valor del enumerador original y el el valor del campo de bits se comparará igual.
[ Example:

enum BOOL { FALSE=0, TRUE=1 };
struct A {
  BOOL b:1;
};
A a;
void f() {
  a.b = TRUE;
  if (a.b == TRUE)    // yields true
    { /* ... */ }
}

— end example ]


A primera vista, la parte en negrita parece abierta a la interpretación. Sin embargo, la intención correcta se vuelve clara cuando el enum BOOL se deriva de la int.

enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = TRUE;
  if(s.enabled == TRUE)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

Con el código anterior da una advertencia sin -Wall -pedantic:

advertencia: ‘mystruct::enabled’ es demasiado pequeño para contener todos los valores de ‘enum BOOL’
struct mystruct { BOOL enabled:1; };

La salida es:

Está desactivado !! (cuando usas enum BOOL : int)

Si enum BOOL : int se hace simple enum BOOLentonces la salida es como especifica el pasaje estándar anterior:

Está habilitado (al usar enum BOOL)


Por lo tanto, se puede concluir, también como pocas otras respuestas, que int type no es lo suficientemente grande como para almacenar el valor “1” en un solo campo de bits.

avatar de usuario
ar18

No hay nada malo con su comprensión de los campos de bits que puedo ver. Lo que veo es que redefiniste mystruct primero como struct mystruct { int habilitado: 1; } y luego como estructura mystruct s;. Lo que deberías haber codificado era:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
    mystruct s; <-- Get rid of "struct" type declaration
    s.enabled = 1;
    if(s.enabled == 1)
        printf("Is enabled\n"); // --> we think this to be printed
    else
        printf("Is disabled !!\n");
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad