De nombreux guides de style, tels que celui de Google, recommandent d'utiliser int
comme entier par défaut lors de l'indexation de tableaux par exemple. Avec l'avènement des plateformes 64 bits où la plupart du temps un int
est seulement 32 bits, ce qui n'est pas la largeur naturelle de la plateforme. En conséquence, je ne vois aucune raison, à part la simplicité, de conserver ce choix. Nous voyons clairement cela en compilant le code suivant:
double get(const double* p, int k) {
return p[k];
}
qui est compilé en
movslq %esi, %rsi
vmovsd (%rdi,%rsi,8), %xmm0
ret
où la première instruction promeut l'entier de 32 bits en un entier de 64 bits.
Si le code est transformé en
double get(const double* p, std::ptrdiff_t k) {
return p[k];
}
l'assemblage généré est maintenant
vmovsd (%rdi,%rsi,8), %xmm0
ret
ce qui montre clairement que le CPU se sent plus à l'aise avec std::ptrdiff_t
qu'avec un int
. De nombreux utilisateurs de C++ sont passés à std::size_t
, mais je ne veux pas utiliser d'entiers non signés sauf si j'ai vraiment besoin d'un comportement de modulo 2^n
.
Dans la plupart des cas, utiliser int
n'a pas d'impact sur les performances car le comportement indéfini ou les débordements d'entiers signés permettent au compilateur de promouvoir internalement n'importe quel int
en std::ptrdiff_t
dans les boucles traitant des indices, mais nous voyons clairement à partir de ce qui précède que le compilateur ne se sent pas à l'aise avec int
. De plus, utiliser std::ptrdiff_t
sur une plateforme 64 bits rendrait moins probable l'apparition de débordements car je vois de plus en plus de personnes piégées par les débordements d'int
lorsqu'elles doivent traiter des entiers plus grands que 2^31 - 1
qui deviennent vraiment courants de nos jours.
De ce que j'ai vu, la seule chose qui distingue int
semble être le fait que les littéraux tels que 5
sont des int
, mais je ne vois pas où cela pourrait poser problème si nous passons à std::ptrdiff_t
comme entier par défaut.
Je suis sur le point de faire de std::ptrdiff_t
l'entier standard de facto pour tout le code écrit dans ma petite entreprise. Y a-t-il une raison pour laquelle ce pourrait être un mauvais choix?
PS: _Je suis d'accord sur le fait que le nom std::ptrdiff_t
est laid, c'est la raison pour laquelle je l'ai typedefié en il::int_t
qui semble un peu mieux._
PS: Comme je sais que beaucoup de gens me recommanderont d'utiliser std::size_t
comme entier par défaut, je tiens vraiment à préciser que je ne veux pas utiliser un entier non signé comme entier par défaut. L'utilisation de std::size_t
comme entier par défaut dans la STL a été une erreur, comme l'ont reconnu Bjarne Stroustrup et le comité de normalisation dans la vidéo Interactive Panel: Ask Us Anything à 42:38 et 1:02:50.
PS: _En termes de performances, sur n'importe quelle plateforme 64 bits que je connais, +
, -
et *
sont compilés de la même manière pour int
et std::ptrdiff_t
. Il n'y a donc aucune différence de vitesse. Si vous divisez par une constante au moment de la compilation, la vitesse est la même. C'est seulement lorsque vous divisez a/b
sans savoir quoi que ce soit sur b
que l'utilisation d'un entier de 32 bits sur une plateforme 64 bits vous donne un léger avantage en termes de performances. Mais ce cas est si rare que je ne vois pas pourquoi je devrais m'éloigner de std::ptrdiff_t
. Lorsque nous traitons du code vectorisé, là il y a une différence claire, et plus petit est mieux, mais c'est une autre histoire, et il n'y aurait aucune raison de rester avec int
. Dans ces cas, je recommanderais d'utiliser les types de taille fixe de C++._
34 votes
"De nombreux guides de styles, tels que celui de Google, recommandent d'utiliser int comme entier par défaut, par exemple pour l'indexation des tableaux" - citation nécessaire. Vous devriez toujours utiliser
size_t
ousize_type
(STL).30 votes
"qui est compilé en" - Avec quels drapeaux et par quel compilateur?
33 votes
En utilisant
int
pour indexer dans les tableaux est simplement incorrect car il n'y a aucune garantie queint
soit assez grand pour couvrir tous les indices possibles dans un tableau.std::size_t
est le bon type pour cela.4 votes
Cette promotion n'est nécessaire que parce que l'autre opérande est un pointeur et parce que l'entier est passé à travers une interface de fonction non en ligne. Dans un code "entier normal", vous ne devriez voir aucune différence.
75 votes
Le guide de style de Google est principalement nul. Je resterais loin de lui.
34 votes
Je ne veux pas utiliser des entiers non signés à moins que je n'aie vraiment besoin du comportement modulo 2^n. -- vous ne voulez pas non plus passer un index de tableau négatif... (Comportement indéfini en C)
4 votes
Les entiers utilisés dans les index de tableau sont également utilisés dans d'autres opérations mathématiques qui pourraient donner des valeurs négatives. Par exemple, soustraire un index de l'autre.
7 votes
@DavidC.Rankin Traiter les index comme signés simplifie parfois les algorithmes. Vous pouvez écrire, par exemple,
while (i >= 0) ...
et effectuer quelques soustractions à l'intérieur de la boucle.13 votes
@DavidC.Rankin: Comment un type non signé aide-t-il en cela? Au lieu de UB en raison d'un index négatif dans
a[i]
, vous obtiendrez soit UB en raison d'un index positif hors limites, soit un comportement défini mais indésirable. Le bug s'est déjà produit avant cela;i
ne doit pas être négatif. Vous devriez donc utiliser un nombre signé afin de pouvoir détecter l'erreur (par exemple,assert(i >= 0);
).1 votes
Quoi?? Vous ne pouvez pas présumer qu'utiliser un index non signé entraînera un UB en raison d'une sortie de limites positive à moins d'être assez stupide pour ne pas protéger les limites de votre tableau. L'un ne suit pas toujours l'autre et je ne dis pas que vous ne voulez pas utiliser
int
comme index - mon Dieu, c'est fait tout le temps. Le commentaire était une réponse à la citation et rien de plus.8 votes
@DavidC.Rankin : Je ne comprends pas ce que vous essayez de dire.
1 votes
Il s'agit simplement d'une analogie rhétorique. vous ne voulez pas plus vous abstenir d'utiliser un index non signé que vous ne voulez passer un index négatif. Désolé pour le paradoxe philosophique.
3 votes
Comment "définir un entier par défaut" pourrait-il avoir du sens ? Ce n'est pas comme s'il y avait des "exigences par défaut" pour toute variable ou plage d'application...
0 votes
@Andreas : Par exemple,
auto i = 0;
.3 votes
Personnellement, je préfère utiliser int32_t, uint64_t et similaires, car il est très clair comment votre variable est censée se comporter en ce qui concerne signé/non signé et taille en mémoire
1 votes
Herb Sutter a écrit dans les directives de base de C++ que ptrdiff_t est recommandé. Voir la réponse.
1 votes
Pas une réponse, mais chaque fois que ce sujet est abordé, je me souviens de cet article : viva64.com/en/a/0050
6 votes
IMO Je sais que certains pensent que
std::ptrdiff_t
est le meilleur choix parce qu'il est signé. Je trouve les raisons de choisir signed plutôt que unsigned peu convaincantes. En réalité, cela causera de réels problèmes pour quiconque utilise leSTL
(ce qui devrait être tout le monde) et les éventuels avantages sont marginaux par rapport à la complexité dans l'ensemble du code source. Ma philosophie est d'utiliser le type naturel pour le travail. Les tableaux n'ont pas de indexes négatifs. Traitez les math différemment de l'indexation.1 votes
Je n'ai jamais lu le guide de style Google - J'ai peur, ou plus précisément, de son impact probable sur ma tension artérielle.
0 votes
Pour commencer, les indices négatifs/débordés entraînent généralement des échecs spectaculaires. De telles erreurs sont facilement corrigées et préférables aux bugs sournois qui nécessitent beaucoup d'efforts pour être déterrés. Si un indice est censé aller de 0 à 255, j'utiliserai un int, et je me moque de ce que dit Google:)
0 votes
Il y a une question ici concernant un
int
signé utilisé dans une boucle qui pourrait éventuellement être due à l'UB
de l'overflow d'entier signé. stackoverflow.com/questions/48731306/… Au moins l'overflow non signé est bien défini.9 votes
Le titre est bizarre pour moi. Oui, il y en a absolument. Si vous ne pouvez pas penser à une situation où
int
est suffisant (en termes de plage requise), alors vous devez avoir un ensemble de besoins très spécifique et une vision étroite de ce que font les autres avec un langage aussi largement utilisable. Penser que tout le monde devrait avoir à utiliser des monstres verbeux commestd::ptrdiff_t
même dans des situations où c'est excessif est absurde. Si vous parlez uniquement de l'indexation dans des conteneurs, alors corrigez votre titre pour ne pas impliquer toutes les utilisations deint
.0 votes
@underscore_d: Le titre est tout à fait bien. Pouvez-vous donner un exemple où
int
est la meilleure solution (pas simplement suffisante, mais la meilleure) ? Parce que s'il existe une meilleure alternative àint
pour chaque situation (commeptrdiff_t
,intXX_t
,int_leastXX_t
), alors il n'y a aucune raison de l'utiliser.2 votes
Le titre de la question est taquin, la question est en réalité "Y a-t-il une raison d'utiliser int pour l'indexation en C++", la réponse est triviale et il y a déjà de nombreuses réponses sur SO à ce sujet. J'espère que ce comportement ne deviendra pas viral.
4 votes
@JesperJuhl : Le guide de style Google est conçu pour être utilisé chez Google. Il fonctionne très bien à cette fin spécifique. Il n'est pas destiné à une consommation générale, à moins que vous ne vous apprêtiez à commettre du code dans l'un des projets open source de Google (ou si vous avez une peur irrationnelle des exceptions).
0 votes
@Kevin était d'accord. Ce qui signifie aussi que c'est (probablement) nul pour la plupart des utilisations non Google, car il est écrit pour un cas d'utilisation très spécifique.
1 votes
@geza Imprimez les nombres de 1 à 10 :
for(int i = 1; i <= 10; i++) printf("%d\n", i);
0 votes
@SteveSummit : Je accepte cet exemple :) C'est une occasion rare où
int
est bien. Mais, si vous êtes dans un programme un peu plus grand, et avez un typedef d'entier signé sur 16 bits à nom court (commes16
), c'est tout aussi bon queint
. Et, si vous voulez imprimer des nombres de 1 à 50000 de manière portable, vous ne pouvez plus utiliserint
. Mais vous pouvez utilisers32
.3 votes
Moins d'instructions != code plus rapide. Vous devez mesurer pour le découvrir. Par exemple, quel est le coût de l'augmentation de la taille des données lors de l'utilisation d'index 64 bits? Obtenez-vous moins de registres disponibles? Empêchez-vous le compilateur de paralléliser les opérations de données sur 32 bits? Est-ce que tout cela a même de l'importance à la fin?
0 votes
@geza Je ne vais pas m'engager dans une longue discussion à ce sujet, mais j'espère que tu sais que
int16_t
est un choix significativement pauvre pour ce genre de chose.0 votes
@SteveSummit: pour votre exemple, il n'y a rien de mal à utiliser
int16_t
. Avec un compilateur optimisant, vous obtiendrez les mêmes résultats qu'avecint
. Et vous pouvez utiliserint_fast16_t
, si vous craignez une dégradation des performances. De plus, il existe des plates-formes 64 bits (comme PowerPC), où les performances deint
ne sont pas idéales, car il est en 32 bits. Malheureusement,int
ne signifie pas la largeur du registre pour les architectures 64 bits. J'ai fait des optimisations en remplaçantint
pars64
.1 votes
movslq
n'est nécessaire que parce que la convention d'appel ABI x86-64 autorise la présence de données non utiles dans les 32 bits supérieurs des registres utilisés pour passer des arguments de 32 bits. Si vous faites quelque chose commeint k = 4; return p[k];
, il n'y aura pas besoin de l'étendre avec signe.4 votes
En forçant l'utilisation d'un type 64 bits là où un type 32 bits suffirait, vous doublez immédiatement l'empreinte de votre cache de données, les besoins de débit mémoire, et ainsi de suite. Cela semble assez insensé.
0 votes
@DavidSchwartz : si vous avez répondu à mon commentaire : je parle des variables locales qui sont placées dans des registres ici, pas de la mémoire. Bien sûr, on devrait utiliser le type de données le plus petit pour stocker en mémoire. Mais sur un processeur 64 bits, qui n'a pas la capacité intrinsèque d'accéder aux 32 bits inférieurs/supérieurs d'un registre (par exemple, PowerPC), les
int
de 32 bits ne sont pas idéaux (en raison des masquages de 32 bits et des extensions de signe de 64 bits nécessaires).1 votes
1. De nombreuses API utilisent
int
, y compris des parties de la bibliothèque standard. 2. En raison des règles de promotion de type par défaut, les types plus étroits queint
pourraient être élargis enint
ouunsigned int
sauf si vous ajoutez des conversions explicites à de nombreux endroits, et divers types pourraient être plus étroits queint
sur une certaine implémentation. Ainsi, si vous vous souciez de la portabilité, c’est un petit casse-tête.1 votes
@DavidC.Rankin Donc selon vous, le code suivant est invalide (je crois que SE utilise markdown donc j'espère que je peux le faire fonctionner) :
char str[]="Hello"; char *p = str + 1; p[-1] = 'W';
? Parce qu'en réalité, c'est parfaitement valide. Parfois, invalide ne signifie pas toujours invalide. Donc à moins que ce ne soit un changement absurde en C++ par rapport à C...0 votes
Il n'est pas très clair d'après votre question si vous parlez de cet endroit en particulier ou en général. Si c'est en général, pour les grandes données et si votre algorithme est limité par la mémoire, économiser 50% de bande passante/$ peut être important (voir par exemple l'ABI x32). Je ne me préoccuperais pas de l'assembly - le mouvement esi->rsi est 'bon marché' et pourrait être optimisé dans les uops (l'assembly x86 est JITté vers une représentation interne) tandis que le chargement prend un minimum de 4 cycles (L1 hit) et n'a pas de limite supérieure (faute de page avec échange sur un périphérique de sauvegarde lent).
1 votes
"Le recours par défaut à
int
" est un héritage corrompu du C préhistorique. Le type par défaut dans le code C++ moderne doit être unsigned.0 votes
@Pryftan tant que
p[-1]
(ou*(p - 1)
) satisfait C11 §6.5.6 Opérateurs additifs (p8) il n'y a pas de problème. Le commentaire original était destiné dans le contexte d'une déclaration où tenter de déclarer un tableau de taille nulle ou moins est indéfini.0 votes
@DavidC.Rankin C'est juste. Je pensais plus généralement. Il faut dire que hier était une journée particulièrement affreuse - ou plutôt je me sentais très mal. Tu as bien sûr raison dans ta clarification. Et oui bien sûr cela se traduit par
*(p-1)
mais j'utilisais la syntaxe du tableau compte tenu du contexte... Merci de clarifier et je m'excuse si j'ai été présomptueux (ce n'était pas mon intention même si en y repensant à hier je pense que c'était en fait le cas - même si seulement subtilement ou à peine).0 votes
@Pryftan En utilisant
char
pour les données de caractères, vous ouvrez une autre boîte de Pandore ;)0 votes
@HagenvonEitzen Veuillez élaborer. J'ai deux pensées: c'est un jeu de mots que je ne comprends pas ou vous pensez à C++ et sa chaîne STL. Pour information, j'ai eu une mauvaise réaction à C++ et bien que je comprenne l'intention de std::string et bien que je l'ai utilisé je préfère C et rien d'autre. Il y a bien sûr une troisième option: c'est quelque chose d'entièrement différent. Alors lequel est-ce?
0 votes
@DanielLangr Ne vous embêtez pas à écrire
for(i = count - 1; i >= 0; i--)
, écrivez simplementfor(i = count; i--; )
. Fonctionne indépendamment de sii
est signé ou non signé, et vous économisez dix caractères...1 votes
À mon avis, c'était une erreur de ne pas faire évoluer
int
à 64 bits. Il aurait toujours dû être le type entier naturel du CPU. Tel quel, nous nous retrouvons sans un type défini comme le type entier naturel du CPU. Cependant, si vous considérezsize_t
comme un substitut non signé, vous pouvez également considérerssize_t
comme un substitut signé. Pas besoin de laisser tomber ce terribleptrdiff_t
partout.0 votes
@cmaster: Si
int
était un integer de 64 bits, il n'y aurait pas eu assez de place pour les integers de 8, 16 et 32 bits car nous n'avons quechar
etshort
en dessous deint
. Mais un nouveau nom aurait pu être inventé. De plus, je pense qu'il y avait des raisons historiques pour le maintenir à 32 bits, certaines d'entre elles étant liées à Fortran. Mais je suis d'accord avec vous, ce serait tellement bien siint
était toujours le type entier naturel du CPU. En ce qui concernessize_t
, il ne semble pas être dans la norme.0 votes
ssize_t
est le type de retour deread()
, et est donc une norme POSIX.1-2001. Il pourrait ne pas être disponible sur Windows, cependant. Je ne saurais dire. En ce qui concerne la nécessité de nouveaux noms, je ne le pense pas : nous avons les types de taille fixeint8_t
,int16_t
etint32_t
; rien n'a jamais suggéré que ces tailles devaient être disponibles en tant quechar
,short
etint
. Les seules garanties de la norme C sont que1 == sizeof(char) <= sizeof(short) <= sizeof(int)
, et queint
fait au moins 16 bits. Rien n'indique que la séquence de types ne doit pas avoir de trous.0 votes
@cmaster Je n'ai pas dit que je décrémentais
i
de un seulement. Par exemple, dans certains algorithmes multi-thread, les données sont traitées par blocs et certains index atomiques globaux sont décrémentés par une taille de bloc (fetch et sub). Utiliser des entiers non signés ici pourrait nécessiter plus deif
conditions à l'intérieur du code.0 votes
@cmaster En ce qui concerne le modèle
for(i = count; i--; )
, même s'il fonctionne, je crois que c'est un désastre dans un code car il est difficile à lire. Je le considérerais comme un anti-modèle. Ce qui me fait crier, c'est l'horrible code que les gens sont prêts à taper pour défendre les entiers non signés. Il n'y a pas une seule raison valable de les défendre. Même sur un système 32 bits, lorsque les gens veulent unstd::vector
dont la longueur est >= 2^31, la STL ne peut pas le fournir car leur implémentation utilise deux pointeurs, un au début, un à leur fin. La taille estfin - début
ce qui donne.... unptrdiff_t
.0 votes
@InsideLoop Dans ce cas, j'ai une mauvaise nouvelle pour vous : Votre ordinateur ne peut pas calculer avec des entiers signés. Il ne peut pas les additionner, les soustraire, les multiplier, il ne peut que les diviser. Chaque et chaque
int
signé que vous utilisez dans votre code est traité comme non signé par le matériel jusqu'à ce que vous divisez ou comparez. Ensuite, et seulement ensuite, le matériel interprète le motif de bits comme un nombre signé. Lorsque vous faitesfin-début
, le CPU calcule la différence correctement en tant qu'arithmétique non signée modulo2^32
. C'est à 100 % de votre faute et de votre problème que vous interprétez ensuite ce résultat comme signé.0 votes
@cmaster Je suis d'accord avec vous que chaque opération telle que
+
,-
et*
sont traitées modulo2^n
au niveau de l'assemblage. L'avantage des entiers signés par rapport aux non signés se situe au niveau de la représentation intermédiaire où le compilateur optimise le code. Pour la taille maximale d'unstd::vector
, si vous utilisez libc++ (clang) sur une plate-forme 32 bits, la méthodemax_size()
renverra 2 147 483 647 qui est le plus grand entier signé. Avec libstdc++ (gcc), vous obtiendrez 4 294 967 295. La méthodesize
calculefin - début
qui est unptrdiff_t
qui est casté vers unsize_t
.0 votes
@cmaster Donc le dépassement de capacité pourrait se produire dans
libstdc++
pour des vecteurs de char de taille supérieure à 2 147 483 648 sur une plate-forme 32 bits. Je suis d'accord que gcc et la plupart des compilateurs traiteront ce comportement indéfini de manière à obtenir le résultat correct. Mais je crois fermement quelibstdc++
ne respecte pas la norme ici tandis quelibc++
le fait.0 votes
@cmaster La norme indique que la différence de 2 pointeurs est un
ptrdiff_t
qui est un entier signé. Ainsi,end - begin
devrait être traité comme un entier signé, et non comme un entier non signé.0 votes
@InsideLoop Pour être précis,
ptrdiff_t
doit être défini comme une quantité signée : Quand vous faitesend-begin
, le matériel calcule en réalité(end-begin)/sizeof(*begin)
. Il s'agit d'une division, et cette division doit être effectuée en arithmétique signée en général. En fait, j'ai eu tort dans mon dernier commentaire : Ce n'est pas votre faute, mais la faute du langage pour définir la différence de pointeur de la manière dont il le fait. Une implémentation correcte devrait d'abord comparer les deux pointeurs, puis calculer la différence absolue, et réattacher le signe ensuite. Mais cela serait très inefficace.0 votes
@cmaster Je ne pensais pas au problème de division. Comme je pensais à un tableau de caractères, de toute façon ce n'est pas un problème car
sizeof(char)
est égal à un.0 votes
@InsideLoop Oui, mais
char
est un cas très particulier. Et c'est une bonne chose quecharPtrA - charPtrB
donne le même type quefooPtrA - fooPtrB
dans tous les cas, à mon avis. Nous avons suffisamment de cas particuliers dans n'importe quel langage, si vous me demandez, nous ne devrions pas en ajouter d'inutiles.0 votes
@cmaster Néanmoins, si
p1
etp0
sont deux pointeurs du même type, la différence est unptrdiff_t
qui est signé. Par conséquent, les indices sont naturellement signés enC/C++
. Je pense que ce choix a été fait car demander quep1
soit "plus grand" quep0
n'était pas une exigence pratique pour calculerp1 - p0
. Quoi qu'il en soit, ma préférence pour le signé plutôt que pour l'unsigned est principalement liée au "comportement indéfini" du débordement signé qui permet de nombreuses optimisations du compilateur.