Une interdiction "longue" a-t-elle un sens?

109

Dans le monde multi-plateforme C ++ (ou C) actuel, nous avons :

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

Ce que cela signifie aujourd’hui, c’est que, pour tout entier «commun» (signé), intcela suffira et peut toujours être utilisé comme type d’entier par défaut lors de l’écriture du code d’application C ++. Pour les besoins pratiques actuels, il aura également une taille uniforme sur toutes les plateformes.

Ssi un cas d'utilisation nécessite au moins 64 bits, on peut utiliser aujourd'hui long long, mais peut - être en utilisant l' un des types de bitness spécifiant ou le __int64type peut plus logique.

Cela laisse longau milieu, et nous envisageons d'interdire carrément l'utilisation de longnotre code d'application .

Est-ce que cela aurait du sens ou est-il judicieux d’utiliser longdu code C ++ (ou C) moderne qui doit fonctionner sur plusieurs plates-formes? (la plate-forme étant un ordinateur de bureau, des appareils mobiles, mais pas des choses comme des microcontrôleurs, des DSP, etc.)


Liens de fond éventuellement intéressants:

Martin Ba
la source
14
Comment allez-vous gérer les appels aux bibliothèques longues?
Angel
14
longest le seul moyen de garantir 32 bits. intpeut être 16 bits, donc pour certaines applications, cela ne suffit pas. Oui, intc’est parfois 16 bits sur les compilateurs modernes. Oui, les gens écrivent des logiciels sur des microcontrôleurs. Je dirais que plus de gens écrivent des logiciels qui ont plus d'utilisateurs sur des microcontrôleurs que sur des PC avec la montée des appareils iPhone et Android, sans parler de la montée des Arduinos, etc.
slebetman
53
Pourquoi ne pas interdire char, short, int, long et long long et utiliser les types [u] intXX_t?
user253751
7
@slebetman J'ai creusé un peu plus profondément, il apparaît que l'exigence est toujours en place, bien que masquée dans le §3.9.1.3 où le standard C ++ indique: "Les types entiers signés et non signés doivent satisfaire aux contraintes données dans la norme C, section 5.2. 4.2.1. " Et dans la norme C §5.2.4.2.1, il indique la plage minimale, exactement comme vous l'avez écrit. Tu avais absolument raison. :) Apparemment, posséder une copie de la norme C ++ n'est pas suffisant, il faut également trouver la copie de la norme C.
Tommy Andersen
11
Il vous manque le monde DOSBox / Turbo C ++ dans lequel il intreste encore beaucoup 16 bits. Je déteste le dire, mais si vous écrivez à propos du "monde multiplateforme d'aujourd'hui", vous ne pouvez pas ignorer le sous-continent indien dans son ensemble.
Courses de légèreté en orbite

Réponses:

17

La seule raison pour laquelle j'utiliserais longaujourd'hui est lorsque vous appelez ou implémentez une interface externe qui l'utilise.

Comme vous le dites dans votre message, short et int ont des caractéristiques raisonnablement stables sur toutes les principales plates-formes de bureau / serveur / mobile actuelles et je ne vois aucune raison pour que cela change dans un avenir proche. Je vois donc peu de raisons de les éviter en général.

longde l'autre côté est un gâchis. Je sais que sur tous les systèmes 32 bits, il présentait les caractéristiques suivantes.

  1. C'était exactement 32 bits.
  2. C'était la même taille qu'une adresse mémoire.
  3. C'était la même taille que la plus grande unité de données pouvant être conservée dans un registre normal et travaillant avec une seule instruction.

De grandes quantités de code ont été écrites en fonction d’une ou de plusieurs de ces caractéristiques. Cependant, avec le passage à la version 64 bits, il n’a pas été possible de toutes les conserver. Les plates-formes de type Unix ont opté pour LP64 qui a conservé les caractéristiques 2 et 3 au détriment de la caractéristique 1. Win64 a opté pour LLP64 qui a conservé la caractéristique 1 au détriment des caractéristiques 2 et 3. Il en résulte que vous ne pouvez plus vous en servir. et que l’OMI laisse peu de raisons d’utiliser long.

Si vous voulez un type dont la taille est exactement 32 bits, vous devriez utiliser int32_t.

Si vous voulez un type de la même taille qu'un pointeur, vous devez utiliser intptr_t(ou mieux uintptr_t).

Si vous voulez un type qui soit l'élément le plus volumineux pouvant être traité dans un seul registre / instruction, je ne pense malheureusement pas que la norme en fournisse un. size_tdevrait être juste sur la plupart des plateformes courantes mais ce ne serait pas sur x32 .


PS

Je ne m'embêterais pas avec les types "rapide" ou "le moins". Les types "les moins" ne comptent que si vous vous souciez de la portabilité pour vraiment obscurcir les architectures où CHAR_BIT != 8. La taille des types "rapides" dans la pratique semble être assez arbitraire. Linux semble leur donner au moins la même taille que le pointeur, ce qui est ridicule sur les plates-formes 64 bits avec un support 32 bits rapide comme x86-64 et arm64. IIRC iOS les rend aussi petites que possible. Je ne suis pas sûr de ce que font les autres systèmes.


PPS

Une des raisons d'utiliser unsigned long(mais pas clairement long) est parce qu'il est gaurante d'avoir un comportement modulo. Malheureusement, en raison de la confusion des règles de promotion de C, les types non signés plus petits que ceux qui intn'ont pas de comportement modulo.

Aujourd'hui, sur toutes les grandes plates-formes, uint32_tla taille est supérieure ou égale à celle de l'int et a donc un comportement modulo. Cependant, il y a eu historiquement et il pourrait théoriquement y avoir dans les futures plates-formes où intest 64 bits et uint32_tn'a donc pas de comportement modulo.

Personnellement, je dirais qu'il est préférable de forcer le comportement modulo en utilisant "1u *" ou "0u +" au début de vos équations car cela fonctionnera pour toute taille de type non signé.

Peter Green
la source
1
Tous les types "de taille spécifiée" seraient beaucoup plus utiles s'ils pouvaient spécifier une sémantique différente des types intégrés. Par exemple, il serait utile d'avoir un type qui utiliserait mod-65536 arithmétique quelle que soit la taille de « int », avec un type qui serait capable de contenir les numéros 0 à 65535 mais pourrait arbitrairement et de manière cohérente pas nécessairement être en mesure de détenir des nombres plus grands que cela. Le type de taille le plus rapide sur la plupart des machines dépend du contexte. Par conséquent, pouvoir laisser le compilateur choisir arbitrairement serait optimal pour la vitesse.
Supercat
204

Comme vous l'avez mentionné dans votre question, un logiciel moderne consiste essentiellement à interopérer entre plates-formes et systèmes sur Internet. Les normes C et C ++ donnent des plages pour les tailles de type entier, et non des tailles spécifiques (contrairement aux langages tels que Java et C #).

Pour que votre logiciel compilé sur différentes plates-formes fonctionne avec les mêmes données de la même manière et que d'autres logiciels puissent interagir avec votre logiciel avec les mêmes tailles, vous devez utiliser des entiers de taille fixe.

Entrez <cstdint>ce qui fournit exactement cela et constitue un en-tête standard que toutes les plateformes de compilateur et de bibliothèque standard doivent fournir. Remarque: cet en-tête n'était requis qu'à partir de C ++ 11, mais de nombreuses implémentations de bibliothèques plus anciennes le fournissaient quand même.

Vous voulez un entier non signé 64 bits? Utilisez uint64_t. Nombre entier 32 bits signé? Utilisez int32_t. Bien que les types figurant dans l'en-tête soient facultatifs, les plates-formes modernes devraient prendre en charge tous les types définis dans cet en-tête.

Parfois, une largeur de bits spécifique est nécessaire, par exemple, dans une structure de données utilisée pour communiquer avec d'autres systèmes. D'autres fois ce n'est pas. Pour les situations moins strictes, <cstdint>fournit des types de largeur minimale.

Il y a moins de variantes: il int_leastXX_ts'agira d'un type entier d'au moins XX bits. Il utilisera le plus petit type fournissant XX bits, mais il est permis que le type soit plus grand que le nombre de bits spécifié. En pratique, ce sont généralement les mêmes que ceux décrits ci-dessus qui donnent le nombre exact de bits.

Il existe également des variantes rapides : int_fastXX_tau moins XX bits, mais il convient d'utiliser un type rapide sur une plate-forme donnée. La définition de "rapide" dans ce contexte n'est pas spécifiée. Cependant, dans la pratique, cela signifie généralement qu'un type inférieur à la taille de registre de la CPU peut être associé à un type de la taille de registre de la CPU. Par exemple, l'en-tête de Visual C ++ 2015 spécifie qu'il int_fast16_ts'agit d'un entier de 32 bits, car l'arithmétique de 32 bits est globalement plus rapide sur l'arithmétique x86 que sur 16 bits.

Tout cela est important car vous devriez pouvoir utiliser des types pouvant contenir les résultats des calculs effectués par votre programme, quelle que soit la plate-forme. Si un programme produit des résultats corrects sur une plate-forme mais des résultats incorrects sur une autre en raison de différences de dépassement d'entier, c'est mauvais. En utilisant les types d'entiers standard, vous garantissez que les résultats sur différentes plates-formes seront identiques en ce qui concerne la taille des entiers utilisés (bien entendu, il peut exister d'autres différences entre les plates-formes en plus de la largeur d'entier).

Donc, oui, longdevrait être banni du code C ++ moderne. Donc , devrait int, shortet long long.


la source
20
J'aurais aimé avoir cinq autres comptes pour voter encore plus.
Steven Burnap
4
+1, j'ai eu quelques étranges erreurs de mémoire qui ne se produisent que lorsque la taille d'une structure dépend de l'ordinateur sur lequel vous compilez.
Joshua Snider
9
@Wildcard c'est un en-tête C qui fait également partie de C ++: voyez le préfixe "c" dessus. Il existe également un moyen de mettre les typedefs dans l’ stdespace de noms lorsque vous vous trouvez dans #includeune unité de compilation C ++, mais la documentation que j’ai liée ne le mentionne pas et Visual Studio ne semble pas se soucier de la façon dont j’y accède.
11
Interdire intpeut être ... excessif? . (Je considérerais si le code doit être extrêmement portable dans tous les obscurs (et pas si obscure) plates - formes Banning pour « code de l' application » ne peut pas s'asseoir très bien avec nos devs.
Martin Ba
5
@Snowman #include <cstdint>est requis pour mettre les types std::et (malheureusement) éventuellement éventuellement aussi pour les mettre dans l'espace de noms global. #include <stdint.h>est exactement l'inverse. La même chose s'applique à toute autre paire d'en-têtes C. Voir: stackoverflow.com/a/13643019/2757035 J'aurais aimé que la norme impose à chacun de n'affecter que son espace de noms requis respectif - plutôt que de se plier en apparence aux mauvaises conventions établies par certaines implémentations - mais bon, nous en sommes à présent.
underscore_d
38

Non, interdire les types d'entiers intégrés serait absurde. Cependant, ils ne doivent pas non plus être maltraités.

Si vous avez besoin d'un entier de exactement N bits de large, utilisez (ou si vous avez besoin d'une version). Considérer comme un entier 32 bits et comme un entier 64 bits est tout simplement faux. C'est peut-être le cas sur vos plates-formes actuelles, mais cela repose sur un comportement défini par l'implémentation.std::intN_tstd::uintN_tunsignedintlong long

L'utilisation de types entiers à largeur fixe est également utile pour interagir avec d'autres technologies. Par exemple, si certaines parties de votre application sont écrites en Java et d'autres en C ++, vous souhaiterez probablement faire correspondre les types d'entiers afin d'obtenir des résultats cohérents. (Gardez toujours à l'esprit que les débordements dans Java ont une sémantique bien définie, tandis que les signeddépassements en C ++ représentent un comportement non défini. La cohérence est donc un objectif primordial.) Ils seront également précieux pour l'échange de données entre différents hôtes informatiques.

Si vous n'avez pas besoin exactement de N bits, mais simplement d'un type suffisamment large , envisagez d'utiliser (optimisé pour l'espace) ou (optimisé pour la vitesse). Encore une fois, les deux familles ont aussi leurs homologues.std::int_leastN_tstd::int_fastN_tunsigned

Alors, quand utiliser les types prédéfinis? Eh bien, étant donné que la norme ne spécifie pas précisément leur largeur, utilisez-les lorsque vous ne vous souciez pas de la largeur réelle des bits, mais d’autres caractéristiques.

A charest le plus petit entier adressable par le matériel. Le langage vous oblige en fait à l’utiliser pour aliaser de la mémoire arbitraire. C'est également le seul type viable pour représenter des chaînes de caractères (étroites).

An intsera généralement le type le plus rapide que la machine puisse gérer. Il sera suffisamment large pour pouvoir être chargé et stocké avec une seule instruction (sans masquer ni décaler les bits) et suffisamment étroit pour pouvoir être utilisé avec les instructions matérielles (les plus efficaces). Par conséquent, intest un choix parfait pour transmettre des données et faire du calcul lorsque les débordements ne sont pas un sujet de préoccupation. Par exemple, le type d'énumérations sous-jacent par défaut est int. Ne le changez pas en un entier 32 bits simplement parce que vous le pouvez. De plus, si vous avez une valeur qui ne peut être que –1, 0 et 1, unintest un choix parfait, sauf si vous allez en stocker d'énormes tableaux, auquel cas vous voudrez peut-être utiliser un type de données plus compact au prix de payer un prix plus élevé pour accéder à des éléments individuels. Une mise en cache plus efficace sera probablement rentable. De nombreuses fonctions du système d'exploitation sont également définies en termes de int. Il serait stupide de convertir leurs arguments et leurs résultats dans les deux sens. Tout ce que cela pourrait faire serait d’ introduire des erreurs de débordement.

longsera généralement le type le plus large pouvant être traité avec des instructions machine simples. Cela est particulièrement unsigned longintéressant pour traiter des données brutes et toutes sortes de manipulations de bits. Par exemple, je m'attendrais à voir unsigned longdans la mise en œuvre d'un vecteur de bits. Si le code est écrit avec soin, la largeur du type importe peu (car le code s'adapte automatiquement). Sur les plateformes où le mot machine natif est 32 bits, le tableau de sauvegarde du vecteur de bits étant un tableau deunsignedLes entiers 32 bits sont les plus souhaitables car il serait idiot d'utiliser un type 64 bits qui doit être chargé via des instructions coûteuses uniquement pour décaler et masquer les bits inutiles. D'autre part, si la taille de mot native de la plate-forme est de 64 bits, je souhaite un tableau de ce type, car cela signifie que des opérations telles que «trouver le premier ensemble» peuvent être exécutées jusqu'à deux fois plus vite. Ainsi, le «problème» du longtype de données que vous décrivez, du fait que sa taille varie d'une plate-forme à l'autre, est en fait une fonctionnalité qui peut être utilisée à bon escient. Cela ne devient un problème que si vous considérez les types intégrés comme des types d'une certaine largeur de bit, ce qu'ils ne sont tout simplement pas.

char, intet longsont des types très utiles comme décrit ci-dessus. shortet long longne sont pas aussi utiles parce que leur sémantique est beaucoup moins claire.

5gon12eder
la source
4
L'OP a notamment évoqué la différence de taille longentre Windows et Unix. Je me trompe peut-être, mais votre description de la différence de taille entre longune "fonctionnalité" et non un "problème" me semble logique pour comparer des modèles de données 32 bits et 64 bits, mais pas pour cette comparaison particulière. Dans le cas particulier de cette question, s'agit-il vraiment d'une fonctionnalité? Ou est-ce une caractéristique dans d'autres situations (c'est-à-dire en général) et inoffensive dans ce cas?
Dan Getz
3
@ 5gon12eder: le problème est que des types tels que uint32_t ont été créés dans le but de permettre au code d'être indépendant de la taille de "int", mais l'absence d'un type dont la signification serait "se comporter comme un uint32_t fonctionne sur un fichier 32- bit system "rend l'écriture de code dont le comportement est correctement indépendant de la taille de" int "beaucoup plus difficile que d'écrire du code qui est presque correct.
Supercat
3
Oui, je sais ... c'est de là que vient la malédiction. Les auteurs d'origine ont juste pris le chemin de la résistance au bail, car lorsqu'ils ont écrit le code, les systèmes d'exploitation 32 bits étaient dans plus d'une décennie.
Steven Burnap
8
@ 5gon12eder Malheureusement, Supercat est correct. Tous les types exacts de largeur sont « juste typedefs » et les règles de promotion entier ne pas tenir compte d'entre eux, ce qui signifie que l' arithmétique sur les uint32_tvaleurs sera effectuée comme signé , l' intarithmétique -width sur une plate - forme où intest plus large que uint32_t. (Avec les ABI d'aujourd'hui, ce problème est beaucoup plus susceptible de poser problème uint16_t.)
lundi
9
1er, merci pour une réponse détaillée. Mais: Oh mon Dieu. Votre long paragraphe: " longsera généralement le type le plus large qui puisse être traité avec des instructions machine simples. ..." - et c'est tout à fait faux . Regardez le modèle de données Windows. IMHO, votre exemple suivant entier tombe en panne, car sur Windows x64 long est toujours 32 bits.
Martin Ba
6

Une autre réponse précise déjà les types de cstdint et leurs variantes moins connues.

J'aimerais ajouter à cela:

utiliser des noms de type spécifiques au domaine

Autrement dit, ne déclarez pas vos paramètres et variables comme étant uint32_t(certainement pas long!), Mais des noms tels que channel_id_type, room_count_typeetc.

à propos des bibliothèques

Les bibliothèques tierces qui utilisent longou non peuvent être ennuyantes, surtout si elles sont utilisées comme références ou pointeurs.

La meilleure chose à faire est de faire des wrappers.

Ce que ma stratégie est, en général, est de créer un ensemble de fonctions de type cast qui seront utilisées. Ils sont surchargés pour accepter uniquement les types qui correspondent exactement aux types correspondants, ainsi que les variations de pointeur, etc., dont vous avez besoin. Ils sont définis spécifiques à os / compiler / settings. Cela vous permet de supprimer les avertissements tout en garantissant que seules les "bonnes" conversions sont utilisées.

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

En particulier, avec différents types de primitifs produisant 32 bits, votre choix de int32_tdéfinition peut ne pas correspondre à l'appel de la bibliothèque (par exemple, int vs long sous Windows).

La fonction de type transtypage documente le conflit, fournit une vérification au moment de la compilation sur le résultat correspondant au paramètre de la fonction et supprime tout avertissement ou toute erreur si et seulement si le type réel correspond à la taille réelle impliquée. Autrement dit, il est surchargé et défini si je passe (sous Windows) un int*ou un long*et donne une erreur de compilation, sinon.

Ainsi, si la bibliothèque est mise à jour ou si quelqu'un modifie ce qui channel_id_typeest, cela continue d'être vérifié.

JDługosz
la source
pourquoi le vote négatif (sans commentaire)?
JDługosz
Parce que la plupart des votes sur ce réseau apparaissent sans commentaires ...
Ruslan