Question C: off_t (et autres types entiers signés) valeurs minimale et maximale

Je rencontre parfois un type entier (par exemple, type entier signé off_t ) où il serait utile d’avoir une macro pour ses valeurs minimum et maximum, mais je ne sais pas comment en créer une qui soit réellement portable.

Pour les types entiers non signés, j’avais toujours pensé que c’était simple. 0 pour le minimum et ~0 pour le maximum. Depuis, j’ai lu plusieurs discussions SO différentes qui suggèrent d’utiliser -1 au lieu de ~0 pour la portabilité. Un fil intéressant avec quelques controverses est ici:
c ++ – Est-il prudent d’utiliser -1 pour définir tous les bits sur true? – débordement de stack

Cependant, même après avoir pris connaissance de ce problème, je suis toujours confus. De plus, je cherche quelque chose à la fois conforme aux normes C89 et C99, donc je ne sais pas si les mêmes méthodes s’appliquent. Disons que j’ai eu un type de uint_whatever_t . Est-ce que je ne pourrais pas simplement lancer à 0, puis au complément au niveau des bits? Est-ce que ça irait ?:

 #define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 ) 

Les types entiers signés semblent être plus difficiles à craquer. J’ai vu plusieurs solutions possibles, mais une seule semble être portable. Soit ça ou c’est incorrect. Je l’ai trouvé en recherchant un OFF_T_MAX et un OFF_T_MIN dans Google. Crédit à 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) 

Je n’ai rien trouvé concernant les différents types de représentations d’entiers signés autorisés dans C89, mais C99 contient des notes pour les problèmes de portabilité d’entiers dans le §J.3.5:

Indique si les types entiers signés sont représentés par le signe et la magnitude, le complément à deux ou le complément à un, et si la valeur extraordinaire est une représentation de piège ou une valeur ordinaire (6.2.6.2).

Cela semblerait impliquer que seules les trois représentations de nombres signés de la liste peuvent être utilisées. L’implication est-elle correcte et les macros ci-dessus sont-elles compatibles avec les trois représentations?


D’autres pensées:
Il semble que la macro de type fonction MAX_INT_VAL_STEP() donnerait un résultat incorrect s’il existait des bits de remplissage. Je me demande s’il existe un moyen de contourner cela

En lisant les représentations des nombres signés sur Wikipedia, je me rends compte que pour les trois représentations d’entiers signés, tout MAX de type entier signé serait:
désactiver le bit, tous les bits de valeur activés (tous les trois)
Et son MIN serait soit:
bit de signe activé, tous les bits de valeur activés (signe et magnitude)
signer le bit activé, tous les bits de valeur désactivés (complément de ceux / deux)

Je pense que je pourrais tester le signe et la magnitude en faisant ceci:

 #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 */ ) 

Alors, comme signe et magnitude, le bit de signe est activé et tous les bits de valeur activés ne représenteraient pas le minimum pour off_t dans ce cas ~ (off_t) 0 ? Et pour les compléments minimum / minimum, il me faudrait un moyen de désactiver tous les bits de valeur tout en laissant le bit de signe activé. Aucune idée de comment faire cela sans connaître le nombre de bits de valeur. De plus, est-il garanti que le bit de signe aura toujours une valeur de plus que le bit de valeur le plus significatif?

Merci et s’il vous plaît laissez-moi savoir si c’est trop long un post


EDIT 29/12/2010 17H00 HNE :
Comme indiqué ci-dessous par ephemient pour obtenir la valeur maximale du type non signé, (unsigned type)-1 est plus correct que ~0 ou même ~(unsigned type)0 . D’après ce que je peux comprendre lorsque vous utilisez -1, il correspond exactement à 0-1, ce qui conduit toujours à la valeur maximale dans un type non signé.

En outre, comme la valeur maximale d’un type non signé peut être déterminée, il est possible de déterminer le nombre de bits de valeur contenus dans un type non signé. Nous remercions Hallvard B. Furuseth pour sa macro semblable à une fonction IMAX_BITS () qu’il a postée en réponse à une question sur 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) calcule le nombre de bits d’un entier, et IMAX_BITS ((unsigned_type) -1) calcule le nombre de bits d’un unsigned_type. Jusqu’à ce que quelqu’un implémente des entiers de 4 gigaoctets, de toute façon 🙂

Le cœur de ma question rest toutefois sans réponse: comment déterminer les valeurs minimales et maximales d’un type signé via une macro. Je suis toujours à la recherche de cela. Peut-être que la réponse est qu’il n’y a pas de réponse.

Si vous ne visualisez pas cette question sur StackOverflow dans la plupart des cas, vous ne pourrez pas voir les réponses proposées tant qu’elles n’auront pas été acceptées. Il est suggéré de voir cette question sur StackOverflow .

Étonnamment, C favorise les types jusqu’à int avant les opérations arithmétiques, avec des résultats au moins int calibrés. (De même, les bizarreries incluent 'a' caractère littéral de type int , pas de caractère.)

 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 */ 

Donc #define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 ) n’est pas nécessairement correct.

Je crois que j’ai finalement résolu ce problème, mais la solution n’est disponible que lors de la configure , et non lors de la compilation ou de l’exécution, donc ce n’est toujours pas une idée. C’est ici:

 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)) 

L’idée vient du 6.7.2.1 paragraphe 3:

L’expression qui spécifie la largeur d’un champ de bits doit être une expression constante entière avec une valeur non négative qui ne dépasse pas la largeur d’un object du type qui serait spécifié si les deux points et l’expression étaient omis. Si la valeur est zéro, la déclaration ne doit pas avoir de déclarant.

Je serais très heureux si cela vous donne des idées pour résoudre le problème au moment de la compilation.

Pour les représentations en amplitude de signe, c’est assez facile (pour des types au moins aussi larges int , en tout cas):

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

Malheureusement, les représentations de la magnitude des signes sont plutôt minces sur le terrain;)

Vous voudrez probablement examiner le fichier limits.h (ajouté en C99). Cet en-tête fournit des macros qui doivent être définies pour correspondre aux plages du compilateur. (Soit il est fourni avec la bibliothèque Standard fournie avec le compilateur, soit le remplacement d’une bibliothèque standard par une tierce partie est la solution.)

Réponses rapides seulement:

#define UINT_WHATEVER_T_MAX ( ~ (uint_whatever_t) 0 ) me semble correct, la préférence pour -1 est que uint_whatever_t = -1; est plus concis que uint_whatever_t = ~(uint_whatever_t)0;

(CHAR_BIT * sizeof(t)) ne me semble pas ssortingctement conforme. Vous avez raison en ce qui concerne le remplissage des bits. Cette valeur peut donc être considérablement supérieure à la largeur du type, sauf si Posix indique le contraire à propos de off_t .

En revanche, les types entiers de largeur fixe dans C99 ne doivent pas avoir de bits de remplissage. Par conséquent, pour intN_t vous utilisez un sol plus ferme en utilisant la taille pour en déduire la largeur. Ils sont également garantis complément de deux.

Cela semblerait impliquer que seules les trois représentations de nombres signés de la liste peuvent être utilisées. L’implication est-elle correcte?

Oui. 6.2.6.2/2 énumère les trois significations autorisées du bit de signe, et donc les trois représentations de nombre signé signées.

est le bit de signe garanti d’être toujours un plus significatif que le bit de valeur le plus significatif

Il doit indirectement être plus important que les bits de valeur, du fait (6.2.6.2/2 également) que “chaque bit qui est un bit de valeur doit avoir la même valeur que le même bit dans la représentation d’object du type non signé correspondant. “. Les bits de valeur doivent donc être une plage contiguë commençant au moins significatif.

Cependant, vous ne pouvez pas définir de manière portable uniquement le bit de signe. Lisez les sections 6.2.6.2/3 et / 4 sur les zéros négatifs et notez que, même si l’implémentation utilise une représentation qui les contient en principe, elle n’est pas obligée de les prendre en charge et il n’existe aucun moyen garanti de les générer. Sur une implémentation signe + magnitude, ce que vous voulez est un zéro négatif.

[Edit: oh, j’ai mal lu, vous n’avez besoin de générer cette valeur qu’une fois que vous avez éliminé le signe + magnitude, pour que vous puissiez toujours être OK.

Pour être honnête, cela me semble un peu génial si Posix a défini un type entier et ne lui a pas fourni de limite. Boo à eux. Je choisirais probablement l’ancienne approche de “portage en-tête”, dans laquelle vous placeriez la chose qui fonctionne probablement partout dans un en-tête, et expliquez que quelqu’un devrait probablement le vérifier avant de comstackr le code pour toute implémentation fantaisiste. Comparé à ce qu’ils doivent normalement faire pour que le code de n’importe qui fonctionne, ils vivront heureux avec cela.]

Signé max:

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

En supposant que votre système utilise le complément à deux, le minimum signé devrait être:

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

Celles-ci devraient être totalement portables, sauf que long long est techniquement une extension du compilateur dans C89. Cela évite également le comportement indéfini de la superposition d'un nombre entier signé.

Ce n’est techniquement pas une macro, mais dans la pratique, les éléments suivants doivent toujours être off_t dans un minimum constant pour off_t , ou pour tout type signé, quelle que soit la représentation du signe. Bien que je ne suis pas sûr de ce qui n’utilise pas le compliment des deux, si tant est.

POSIX requirejs un type entier signé pour off_t , les valeurs de largeur exacte signée C99 devraient donc suffire. Certaines plates-formes définissent actuellement OFF_T_MIN (OSX), mais POSIX n’en a malheureusement pas besoin.

 #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; 

Le même est utilisable pour obtenir la valeur maximale.

  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; 

Cela pourrait être transformé en une macro en utilisant autoconf ou cmake cependant.

J’ai utilisé le modèle suivant pour résoudre le problème (en supposant qu’il n’y ait pas de bits de remplissage):

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

Le number_of_bits_in_type est obtenu sous la forme CHAR_BIT * sizeof (type) comme dans les autres réponses.

En gros, nous "poussons" les 1 bits en place, tout en évitant le bit de signe.

Vous pouvez voir comment cela fonctionne. Supposons que la largeur est de 16 bits. Ensuite, nous prenons 1 et le décalons à gauche de 16 - 2 = 14, produisant le motif de bits 0100000000000000 . Nous avons soigneusement évité de décaler un 1 dans le bit de signe. Ensuite, on en soustrait 1, obtenant 0011111111111111 . Vous voyez où ça va? Nous décalons cette gauche de 1 en obtenant 0111111111111110 , évitant à nouveau le bit de signe. Enfin, nous ajoutons 1 pour obtenir 0111111111111111 , qui est la plus haute valeur signée sur 16 bits.

Cela devrait fonctionner correctement sur les machines complémentaires et à taille de signe, si vous travaillez dans un musée où ils ont de telles choses. Cela ne fonctionne pas si vous avez des bits de remplissage. Pour cela, tout ce que vous pouvez faire est #ifdef , ou basculer vers d'autres mécanismes de configuration en dehors du compilateur et du préprocesseur.