web-dev-qa-db-fra.com

Est-il prudent de convertir size_t en unsigned long int?

J'ai besoin d'un moyen portable pour imprimer la valeur d'une variable n de type size_t. Depuis que j'utilise ANSI C89, je ne peux pas utiliser le modificateur de longueur z. Mon approche actuelle consiste à convertir la valeur en long unsigned int:

printf("%lu\n", (long unsigned int) n);

Si size_t est défini comme étant unsigned int ou long unsigned int, je ne vois pas comment cela pourrait échouer. Le casting est-il en sécurité?

8
August Karlstrom

Dans C89, size_t est défini comme un type entier non signé. Contrairement aux futures normes, C89 définit la liste des types d’entiers non signés:

  • caractère non signé
  • court non signé
  • non signé int
  • non signé longtemps

En tant que tel, size_t dans C89 ne sera jamais plus grand que unsigned long et, par conséquent, le transtypage est toujours sûr, à la fois en ce sens qu'il ne provoquera aucun comportement non défini et qu'il sera toujours suffisamment grand pour contenir la valeur dans son intégralité.

À noter; la norme C89 indique: "Une implémentation conforme peut avoir des extensions (y compris des fonctions de bibliothèque supplémentaires), à condition qu’elle ne modifie pas le comportement des programmes strictement conformes" Ce qui signifie qu’aucune extension ne peut changer. Ce comportement - tout en restant conforme à la norme C89, car les types entiers non signés ont été spécifiquement listés et ne peuvent donc pas être modifiés.

Dans les normes futures, ce n'est pas une garantie et même si vous n'obtenez pas de comportement indéfini, vous risquez de perdre des données lorsque unsigned long est inférieur à size_t, ce qui signifie que vous afficheriez des données incorrectes à votre utilisateur. Dans cette situation, j'hésiterais à le qualifier de "sûr".


Comme une note supplémentaire importante; cette réponse fait référence aux compilateurs conformes à la norme C89. Il est possible que votre compilateur C89 soit "moins que conforme" dans les points précédents, auquel cas - considérez que le comportement est similaire à celui de C99 ou plus récent, dans lequel vous ne verrez pas un comportement indéfini, mais vous risquez de perdre des données si size_t est plus grand que unsigned long. Pour être clair cependant, cela ne serait pas conforme à la norme C89.

Au-delà de cela, bien que mon interprétation de la norme (1.7 Conformité) soit telle qu’elle stipule que les extensions ne doivent pas modifier le comportement d’un "programme strictement conforme" et qu’elles ne peuvent donc pas modifier le fait que size_t doit être unsigned long à le plus grand sans se conformer; cela ne change pas le fait que de telles extensions existent. Par exemple, GNU GCC fournit une extension qui ajoute unsigned long long. À mon avis, cela n’est pas conforme, mais en réalité, vous devez être prêt à traiter de telles choses et, en tant que tel - bien que la norme indique que ce que vous faites est complètement sûr, vous devez être préparé à la perte éventuelle de données. des compilateurs ou des extensions compatibles sont utilisés.


Veuillez consulter ici les discussions précédentes sur ce sujet: https://stackoverflow.com/a/39441237/955340

13
Bilkokuya
size_t n = foo();
printf("%lu\n", (long unsigned int) n);

Si size_t est défini comme étant unsigned int ou long unsigned int ... La distribution est-elle sûre?

Oui, le casting est sûr, sans comportement indéfini ni perte d'informations sur C89, C99, C11.


Mais que se passe-t-il si cette condition n'est pas vraie?

En supposant que la plage de size_t sera dans la plage de unsigned long est très raisonnable. Ajouter un test de compilation: ref

#include <limits.h>
#if defined(__STDC__)
#if defined(__STDC_VERSION__)
#if (__STDC_VERSION__ >= 199901L)
#include <stdint.h>
#if SIZE_MAX > ULONG_MAX
#error Re-work printf size code
#endif
#endif
#endif
#endif

Le fait est que lorsque le code avait une dépendance - ajouter un test. Même si cela est acceptable sur toutes les machines connues, aujourd'hui et historiquement, l'avenir est inconnu. 

C, avec son immense souplesse, autorise SIZE_MAX > ULONG_MAX, mais c’est certainement rare. IMO, SIZE_MAX > ULONG_MAX est au-delà de la pâle .


De tels tests sont courants car, bien que possible, il n’est tout simplement pas réalisable ni budgétisé d’écrire du code super portable.

#include <limits.h>
#if CHAR_BIT != 8 && CHAR_BIT != 16 && CHAR_BIT != 32 && CHAR_BIT != 64
  #error Code depends on char size as a common power of 2.
#endif

J'ai besoin d'un moyen portable pour imprimer la valeur d'une variable n de type size_t.

Cependant, pour répondre à l'objectif principal de l'OP, une simple fonction d'assistance portable peut être écrite.

// This approach works with any unsigned type
void print_size_t(size_t n) {
  if (n >= 10) print_size_t(n/10);
  putchar((int) (n%10) + '0');
}

Pour éviter la récursion, une fonction légèrement plus longue: 

#include <limits.h>
void print_size_t(size_t n) {
  char buf[sizeof n * CHAR_BIT / 3 + 2];  // 1/3 is more than log2(10)
  char *p = &buf[sizeof buf - 1];          // Start at end of buf[]
  *p = '\0';
  do {
    p--;
    *p = (char) (n%10 + '0');
    n /= 10;
  } while (n);    // Use a do {} while so print_size_t(0) prints something
  fputs(p, stdout);
}
2
chux