Pregunta C: off_t (y otros tipos de enteros con signo) valores mínimos y máximos

Ocasionalmente me encontraré con un tipo de entero (por ejemplo, POSIX con tipo de entero con off_t ) donde sería útil tener una macro para sus valores mínimos y máximos, pero no sé cómo hacer una que sea verdaderamente portátil.

Para tipos de enteros sin signo siempre había pensado que esto era simple. 0 para el mínimo y ~0 para el máximo. Desde entonces, he leído varios subprocesos SO diferentes que sugieren utilizar -1 lugar de ~0 para la portabilidad. Un hilo interesante con alguna contención es aquí:
c ++ – ¿Es seguro usar -1 para establecer todos los bits en verdadero? – Desbordamiento de stack

Sin embargo, incluso después de leer sobre este tema todavía estoy confundido. Además, estoy buscando algo compatible con C89 y C99, así que no sé si se aplican los mismos métodos. Digamos que tuve un tipo de uint_whatever_t . ¿No podría simplemente convertir a 0 en primer lugar y luego a modo de bits? ¿Estaría bien ?:

 #define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 ) 

Los tipos de enteros con signo parecen ser una tuerca más difícil de romper. He visto varias soluciones posibles diferentes, pero solo una parece ser portátil. O eso o es incorrecto. Lo encontré mientras buscaba en google para OFF_T_MAX y OFF_T_MIN. Crédito a Christian Biere:

 #define MAX_INT_VAL_STEP(t) \ ((t) 1 << (CHAR_BIT * sizeof(t) - 1 - ((t) -1 < 1))) #define MAX_INT_VAL(t) \ ((MAX_INT_VAL_STEP(t) - 1) + MAX_INT_VAL_STEP(t)) #define MIN_INT_VAL(t) \ ((t) -MAX_INT_VAL(t) - 1) [...] #define OFF_T_MAX MAX_INT_VAL(off_t) 

No pude encontrar nada sobre los diferentes tipos permitidos de representaciones enteras con signo en C89, pero C99 tiene notas para problemas de portabilidad de enteros en §J.3.5:

Si los tipos de enteros con signo se representan mediante signo y magnitud, el complemento de dos o el complemento de unos, y si el valor extraordinario es una representación de captura o un valor ordinario (6.2.6.2).

Esto parece implicar que solo se pueden usar las tres representaciones de números firmados enumeradas. ¿Es correcta la implicación y son las macros anteriores compatibles con las tres representaciones?


Otros pensamientos:
Parece que la macro MAX_INT_VAL_STEP() a una función daría un resultado incorrecto si hubiera bits de relleno. Me pregunto si hay alguna forma de evitar esto.

Al leer las representaciones de números con signo en Wikipedia , se me ocurre que para las tres representaciones de enteros con signo, MAX de cualquier tipo de entero con signo sería:
firmar bit off, todos los bits de valor activados (los tres)
Y su MIN sería:
bit de signo activado, todos los bits de valor activado (signo y magnitud)
bit de inicio de sesión, todos los bits de valor desactivados (unidades / dos complementos)

Creo que podría probar el signo y la magnitud haciendo esto:

 #define OFF_T_MIN ( ( ( (off_t)1 | ( ~ (off_t) -1 ) ) != (off_t)1 ) ? /* sign and magnitude minimum value here */ : /* ones and twos complement minimum value here */ ) 

Entonces, como signo y magnitud es el bit de signo activado y todos los bits de valor activados, ¿no sería el mínimo para off_t en ese caso ser ~ (off_t) 0 ? Y para el mínimo de uno / dos, necesitaría alguna forma de desactivar todos los bits de valor pero dejar el bit de signo activado. No tengo idea de cómo hacer esto sin saber el número de bits de valor. ¿También se garantiza que el bit de signo sea siempre más significativo que el bit de valor más significativo?

Gracias y por favor, hágamelo saber si este es un post demasiado largo.


EDITAR 29/12/2010 5PM EST :
Como se indica a continuación por efímero para obtener el valor máximo de tipo sin signo, (unsigned type)-1 es más correcto que ~0 o incluso ~(unsigned type)0 . De lo que puedo recostackr cuando se usa -1, es exactamente igual a 0-1, lo que siempre conducirá al valor máximo en un tipo sin signo.

Además, dado que se puede determinar el valor máximo de un tipo sin signo, es posible determinar cuántos bits de valor están en un tipo sin signo. Gracias a Hallvard B. Furuseth por su macro IMAX_BITS () similar a una función que publicó en respuesta a una pregunta en comp.lang.c

 /* Number of bits in inttype_MAX, or in any (1<<b)-1 where 0 <= b < 3E+10 */ #define IMAX_BITS(m) ((m) /((m)%0x3fffffffL+1) /0x3fffffffL %0x3fffffffL *30 \ + (m)%0x3fffffffL /((m)%31+1)/31%31*5 + 4-12/((m)%31+3)) 

IMAX_BITS (INT_MAX) calcula el número de bits en un int, y IMAX_BITS ((unsigned_type) -1) calcula el número de bits en un unsigned_type. Hasta que alguien implemente enteros de 4 gigabytes, de todos modos 🙂

El corazón de mi pregunta, sin embargo, sigue sin respuesta: cómo determinar los valores mínimos y máximos de un tipo firmado a través de macro. Todavía estoy investigando esto. Tal vez la respuesta es que no hay respuesta.

Si no está viendo esta pregunta en StackOverflow en la mayoría de los casos, no podrá ver las respuestas propuestas hasta que sean aceptadas. Se sugiere ver esta pregunta en StackOverflow .

Sorprendentemente, C promueve tipos hasta int antes de las operaciones aritméticas, y los resultados tienen al menos tamaño int . (De manera similar, las rarezas incluyen 'a' literal de carácter 'a' que tiene el tipo int , no el char ).

 int a = (uint8_t)1 + (uint8_t)-1; /* = (uint8_t)1 + (uint8_t)255 = (int)256 */ int b = (uint8_t)1 + ~(uint8_t)0; /* = (uint8_t)1 + (int)-1 = (int)0 */ 

Así que #define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 ) no está necesariamente bien.

Creo que finalmente he resuelto este problema, pero la solución solo está disponible en tiempo de configure , no en tiempo de comstackción o en tiempo de ejecución, por lo que aún no es una idea. Aquí está:

 HEADERS="#include " TYPE="off_t" i=8 while : ; do printf "%s\nstruct { %sx : %d; };\n" "$HEADERS" "$TYPE" $i > test.c $CC $CFLAGS -o /dev/null -c test.c || break i=$(($i+1)) done rm test.c echo $(($i-1)) 

La idea viene del párrafo 6.7.2.1 3:

La expresión que especifica el ancho de un campo de bits será una expresión constante con un valor no negativo que no exceda el ancho de un objeto del tipo que se especificaría si se omitieran los dos puntos y la expresión. Si el valor es cero, la statement no tendrá declarador.

Me complacería bastante si esto nos llevara a algunas ideas para resolver el problema en tiempo de comstackción.

Para representaciones de magnitud de signo, es bastante fácil (para tipos al menos tan anchos como int , de todos modos):

 #define SM_TYPE_MAX(type) (~(type)-1 + 1) #define SM_TYPE_MIN(type) (-TYPE_MAX(type)) 

Desafortunadamente, las representaciones de magnitud de signo son más bien delgadas en el suelo;)

Probablemente desee ver limits.h (agregado en C99) este encabezado proporciona macros que deben configurarse para que coincidan con los rangos del comstackdor. (O bien se proporciona junto con la biblioteca estándar que viene con el comstackdor, o el reemplazo de una biblioteca estándar de terceros es responsable de hacerlo bien)

Sólo respuestas rápidas:

#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 ) parece bien, la preferencia por -1 es que uint_whatever_t = -1; es más conciso que uint_whatever_t = ~(uint_whatever_t)0;

(CHAR_BIT * sizeof(t)) no se ve estrictamente conforme a mí. Tienes razón sobre el relleno de bits, por lo que este valor podría ser considerablemente mayor que el ancho del tipo a menos que Posix diga lo contrario sobre off_t .

En contraste, los tipos de enteros de ancho fijo en C99 no deben tener bits de relleno, así que para intN_t estás en una posición más firme utilizando el tamaño para deducir el ancho. También están garantizados dos complementos.

Esto parece implicar que solo se pueden usar las tres representaciones de números firmados enumeradas. ¿Es correcta la implicación?

Sí. 6.2.6.2/2 enumera los tres significados permisibles del bit de signo y, por lo tanto, las tres representaciones de números firmados permisibles.

se garantiza que el bit de signo siempre será uno más significativo que el bit de valor más significativo

Se requiere indirectamente que sea más significativo que los bits de valor, por el hecho (6.2.6.2/2 otra vez) que “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 “. Por lo tanto, los bits de valor deben ser un rango contiguo que comience por el menos significativo.

Sin embargo, no se puede establecer de forma portátil sólo el bit de signo. Lea 6.2.6.2/3 y / 4, sobre ceros negativos, y tenga en cuenta que incluso si la implementación usa una representación que los tiene en principio, no tiene que respaldarlos, y no hay una forma garantizada de generar uno. En una implementación de signo + magnitud, lo que desea es un cero negativo.

[Edición: oh, lo he leído mal, solo necesitas generar ese valor después de haber descartado signo + magnitud, por lo que aún podrías estar bien.

Para ser honesto, me suena un poco extraño si Posix ha definido un tipo de entero y no ha proporcionado límites para él. Boo a ellos. Probablemente optaría por el viejo enfoque de “cabecera portadora”, donde colocas lo que probablemente funciona en todas partes en una cabecera, y documenta que alguien debería verificarlo antes de comstackr el código en cualquier implementación anormal. En comparación con lo que normalmente tienen que hacer para que el código de alguien funcione, vivirán felizmente con eso.]

Firmado max:

 #define GENERIC_S_MAX(stype) ((stype) ((1ULL << ((sizeof(stype) * 8) - 1)) - 1ULL)) 

Suponiendo que su sistema utiliza dos complementos, el mínimo firmado debe ser:

 #define GENERIC_S_MIN(stype) ((stype) -1 - GENERIC_S_MAX(stype)) 

Estos deben ser totalmente portátiles, excepto que largo es técnicamente una extensión de comstackdor en C89. Esto también evita el comportamiento indefinido de desbordamiento / subflujo de un entero con signo.

Técnicamente no es una macro, pero en la práctica, lo siguiente siempre se debe plegar en un mínimo constante para off_t , o cualquier tipo con signo, independientemente de la representación del signo. Aunque no estoy seguro de qué es lo que no usa el cumplido de dos, en todo caso.

POSIX requiere un tipo entero con signo para off_t , por lo que los valores de ancho exacto con signo de C99 deberían ser suficientes. Algunas plataformas realmente definen OFF_T_MIN (OSX), pero POSIX desafortunadamente no lo requiere.

 #include  #include  #include  assert(sizeof(off_t) >= sizeof(int8_t) && sizeof(off_t) <= sizeof(intmax_t)); const off_t OFF_T_MIN = sizeof(off_t) == sizeof(int8_t) ? INT8_MIN : sizeof(off_t) == sizeof(int16_t) ? INT16_MIN : sizeof(off_t) == sizeof(int32_t) ? INT32_MIN : sizeof(off_t) == sizeof(int64_t) ? INT64_MIN : sizeof(off_t) == sizeof(intmax_t) ? INTMAX_MIN : 0; 

Lo mismo es utilizable para obtener el máximo valor.

  assert(sizeof(off_t) >= sizeof(int8_t) && sizeof(off_t) <= sizeof(intmax_t)); const off_t OFF_T_MAX = sizeof(off_t) == sizeof(int8_t) ? INT8_MAX : sizeof(off_t) == sizeof(int16_t) ? INT16_MAX : sizeof(off_t) == sizeof(int32_t) ? INT32_MAX : sizeof(off_t) == sizeof(int64_t) ? INT64_MAX : sizeof(off_t) == sizeof(intmax_t) ? INTMAX_MAX : 0; 

Esto podría convertirse en una macro usando autoconf o cmake sin embargo.

He usado el siguiente patrón para resolver el problema (asumiendo que no hay bits de relleno):

 ((((type) 1 << (number_of_bits_in_type - 1)) - 1) << 1) + 1 

El number_of_bits_in_type se deriva como CHAR_BIT * sizeof (type) como en las otras respuestas.

Básicamente, "empujamos" los 1 bits en su lugar, mientras evitamos el bit de signo.

Puedes ver cómo funciona esto. Supongamos que el ancho es de 16 bits. Luego tomamos 1 y lo cambiamos a la izquierda por 16 - 2 = 14, produciendo el patrón de bits 0100000000000000 . Hemos evitado cuidadosamente cambiar un 1 en el bit de señal. A continuación, restamos 1 de esto, obteniendo 0011111111111111 . ¿Ves a dónde va esto? Cambiamos esta izquierda por 1 obteniendo 0111111111111110 , evitando nuevamente el bit de signo. Finalmente, agregamos 1, obteniendo 0111111111111111 , que es el valor más alto de 16 bits con signo.

Esto debería funcionar bien en el complemento de uno y en las máquinas de magnitud de signo, si trabaja en un museo donde tienen tales cosas. No funciona si tienes pedacitos de relleno. Para eso, probablemente todo lo que puede hacer es #ifdef , o cambiar a mecanismos de configuración alternativos fuera del comstackdor y el preprocesador.

    Intereting Posts