Enregistrer le mot clé en C ++

89

Quelle est la différence entre

int x=7;

et

register int x=7;

?

J'utilise C ++.

dato datuashvili
la source
8
@GMan: ANSI C ne permet pas de prendre l'adresse d'un objet registre; cette restriction ne s'applique pas au C ++
Brian R. Bondy
1
@Brian: Hm, tu as raison. C'est juste dans une note maintenant (qu'elle sera probablement ignorée si l'adresse est prise), mais pas obligatoire. Bon à savoir. (Eh bien, en quelque sorte.: P)
GManNickG
8
Le vote pour la réouverture registera une sémantique différente entre C et C ++.
CB Bailey
3
en conséquence, en C, il est possible d'interdire la conversion tableau en pointeur en créant un registre de tableau: register int a[1];avec cette déclaration, vous ne pouvez pas indexer ce tableau. Si vous essayez, vous faites UB
Johannes Schaub - litb
2
En effet, j'ai voté pour la réouverture. J'ai voté pour fermer avant de savoir qu'il y avait une différence.
GManNickG

Réponses:

24

En C ++ tel qu'il existait en 2010, tout programme valide qui utilise les mots-clés "auto" ou "register" sera sémantiquement identique à celui dont ces mots-clés sont supprimés (à moins qu'ils n'apparaissent dans des macros stringized ou d'autres contextes similaires). En ce sens, les mots-clés sont inutiles pour des programmes correctement compilés. D'un autre côté, les mots-clés peuvent être utiles dans certains contextes de macros pour garantir qu'une utilisation incorrecte d'une macro provoquera une erreur de compilation plutôt que de produire du faux code.

Dans C ++ 11 et les versions ultérieures du langage, le automot - clé a été redéfini pour agir comme un pseudo-type pour les objets qui sont initialisés, qu'un compilateur remplacera automatiquement par le type de l'expression d'initialisation. Ainsi, en C ++ 03, la déclaration: auto int i=(unsigned char)5;était équivalente à int i=5;lorsqu'elle était utilisée dans un contexte de bloc, et auto i=(unsigned char)5;était une violation de contrainte. En C ++ 11, auto int i=(unsigned char)5;est devenu une violation de contrainte alors que auto i=(unsigned char)5;est devenu équivalent à auto unsigned char i=5;.

supercat
la source
22
Un exemple du dernier bit peut être utile.
Dennis Zickefoose
14
Cette réponse n'est plus correcte, depuis 2011, le mot-clé autone peut pas être simplement omis ... Peut-être pourriez-vous mettre à jour votre réponse.
Walter le
2
@Walter: Pouvez-vous citer ce qui a changé? Je n'ai pas suivi tous les changements de langue.
supercat du
2
@supercat, oui, pour le moment, mais registerest obsolète et il va y avoir une proposition de le supprimer pour C ++ 17.
Jonathan Wakely
3
Selon en.cppreference.com/w/cpp/language/auto , post C ++ 11, autoest maintenant utilisé pour la déduction automatique de type Mais auparavant, il était utilisé pour spécifier que vous vouliez que votre variable soit stockée "automatiquement" ( donc sur la pile je suppose) par opposition au mot-clé register(signifiant "registre du processeur"):
Guillaume
97

register est un indice pour le compilateur, lui conseillant de stocker cette variable dans un registre de processeur au lieu de la mémoire (par exemple, au lieu de la pile).

Le compilateur peut suivre ou non cette indication.

Selon Herb Sutter dans "Mots clés qui ne sont pas (ou, commentaires d'un autre nom)" :

Un spécificateur de registre a la même sémantique qu'un spécificateur automatique ...

À M
la source
2
Depuis C ++ 17, il est cependant obsolète, inutilisé et réservé.
ZachB
@ZachB, c'est incorrect; register est réservé en C ++ 17 mais il fonctionne toujours et fonctionne presque de la même manière que le registre de C.
Lewis Kelsey
@LewisKelsey Il est inutilisé et réservé dans la spécification C ++ 17; il ne fait pas partie de la storage-class-specifiergrammaire et n'a pas de sémantique définie. Un compilateur conforme peut générer une erreur comme le fait Clang. Néanmoins, certaines implémentations le permettent toujours et l'ignorent (MSVC, ICC) ou l'utilisent comme indice d'optimisation (GCC). Voir open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . J'ai cependant mal parlé sur un point: il était obsolète dans C ++ 11.
ZachB le
26

Avec les compilateurs d'aujourd'hui, probablement rien. C'était à l'origine un indice pour placer une variable dans un registre pour un accès plus rapide, mais la plupart des compilateurs aujourd'hui ignorent cet indice et décident par eux-mêmes.

KeithB
la source
9

Presque certainement rien.

registerest un indice pour le compilateur que vous prévoyez d'utiliser xbeaucoup, et que vous pensez qu'il devrait être placé dans un registre.

Cependant, les compilateurs sont maintenant bien meilleurs pour déterminer les valeurs à placer dans les registres que le programmeur moyen (ou même expert), donc les compilateurs ignorent simplement le mot-clé et font ce qu'ils veulent.

James Curran
la source
7

Le registermot-clé était utile pour:

  • Assemblage en ligne.
  • Programmation experte en C / C ++.
  • Déclaration de variables cachables.

Un exemple de système productif, où le registermot - clé était requis:

typedef unsigned long long Out;
volatile Out out,tmp;
Out register rax asm("rax");
asm volatile("rdtsc":"=A"(rax));
out=out*tmp+rax;

Il est obsolète depuis C ++ 11 et est inutilisé et réservé dans C ++ 17 .

ncomputers
la source
2
Et j'ajouterais que le mot-clé «register» ne serait utile que sur un microcontrôleur exécutant un seul programme C ++ sans thread et sans multi-tâches. Le programme C ++ devrait posséder la totalité du CPU pour s'assurer que la variable «register» ne sera pas déplacée des registres CPU spéciaux.
Santiago Villafuerte
@SantiagoVillafuerte voulez-vous l'ajouter en modifiant la réponse?
ncomputers
Je ne suis pas sûr de ma réponse ... même si cela semble plausible. Je préfère le laisser comme un commentaire afin que les autres l'approuvent ou le désapprouvent.
Santiago Villafuerte
1
@SantiagoVillafuerte Ce n'est pas vrai, dans les systèmes multitâches, lorsque le changement de contexte est le système d'exploitation - pas l'application - est responsable de la sauvegarde / restauration des registres. Comme vous ne changez pas de contexte après chaque instruction du processeur, mettre des choses dans des registres est absolument significatif. Les autres réponses ici (que les compilateurs ne se soucient tout simplement pas de votre opinion en ce qui concerne l'attribution des registres) sont plus précises.
Cubic
L'exemple que vous avez montré utilise en fait l'extension Explicit Register Variables de GCC , qui est différente du registerspécificateur de classe de stockage et est toujours prise en charge par GCC.
ZachB le
1

Prenons un cas où l'optimiseur du compilateur a deux variables et est forcé d'en renverser une sur la pile. Il se trouve que les deux variables ont le même poids pour le compilateur. Étant donné qu'il n'y a pas de différence, le compilateur renverra arbitrairement l'une des variables. D'un autre côté, le registermot - clé donne au compilateur une indication sur la variable qui sera consultée plus fréquemment. Elle est similaire à l'instruction de prélecture x86, mais pour l'optimiseur du compilateur.

De toute évidence, les registerindices sont similaires aux indices de probabilité de branche fournis par l'utilisateur et peuvent être déduits de ces indices de probabilité. Si le compilateur sait qu'une branche est souvent prise, il conservera les variables liées à la branche dans les registres. Je suggère donc de se soucier davantage des indices de branche et d'oublier register. Idéalement, votre profileur devrait communiquer d'une manière ou d'une autre avec le compilateur et vous éviter même de penser à de telles nuances.

SmugLispWeenie
la source
1

Depuis gcc 9.3, la compilation en utilisant -std=c++2a, register produit un avertissement du compilateur, mais il a toujours l'effet désiré et se comporte de la même manière que les C registerlors de la compilation sans les indicateurs d'optimisation -O1 –- Ofast dans le respect de cette réponse. L'utilisation de clang ++ - 7 provoque cependant une erreur du compilateur. Donc oui,register optimisations ne font une différence que sur la compilation standard sans indicateur d'optimisation -O, mais ce sont des optimisations de base que le compilateur trouverait même avec -O1.

La seule différence est qu'en C ++, vous êtes autorisé à prendre l'adresse de la variable de registre ce qui signifie que l'optimisation ne se produit que si vous ne prenez pas l'adresse de la variable ou ses alias (pour créer un pointeur) ou prenez une référence de celui-ci dans le code (uniquement sur - O0, car une référence a également une adresse, car c'est un pointeur const sur la pile , qui, comme un pointeur peut être optimisé hors de la pile en cas de compilation avec -Ofast, sauf n'apparaîtront jamais sur la pile en utilisant -Ofast, car contrairement à un pointeur, ils ne peuvent pas être créés volatileet leurs adresses ne peuvent pas être prises), sinon il se comportera comme vous ne l'aviez pas utilisé register, et la valeur sera stockée sur la pile.

Sur -O0, une autre différence est que const registersur gcc C et gcc C ++ ne se comportent pas de la même manière. Sur gcc C, const registerse comporte comme register, car les portées de bloc constne sont pas optimisées sur gcc. Sur clang C, registerne fait rien et seules constles optimisations de type bloc s'appliquent. Sur gcc C, les registeroptimisations s'appliquent mais constà block-scope n'a pas d'optimisation. Sur gcc C ++, les optimisations registeret constles optimisations de type bloc se combinent.

#include <stdio.h> //yes it's C code on C++
int main(void) {
  const register int i = 3;
  printf("%d", i);
  return 0;
}

int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3
  mov eax, DWORD PTR [rbp-4]
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

register int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  push rbx
  sub rsp, 8
  mov ebx, 3
  mov esi, ebx
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  mov rbx, QWORD PTR [rbp-8] //callee restoration
  leave
  ret

const int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3 //still saves to stack
  mov esi, 3 //immediate substitution
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

const register int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  mov esi, 3 //loads straight into esi saving rbx push/pop and extra indirection (because C++ block-scope const is always substituted immediately into the instruction)
  mov edi, OFFSET FLAT:.LC0 // can't optimise away because printf only takes const char*
  mov eax, 0 //zeroed: https://stackoverflow.com/a/6212755/7194773
  call printf
  mov eax, 0 //default return value of main is 0
  pop rbp //nothing else pushed to stack -- more efficient than leave (rsp == rbp already)
  ret

registerdit au compilateur de 1) stocker une variable locale dans un registre sauvegardé appelé, dans ce cas rbx, et 2) d' optimiser les écritures de pile si l'adresse de la variable n'est jamais prise . constdit au compilateur de remplacer la valeur immédiatement (au lieu de lui attribuer un registre ou de le charger à partir de la mémoire) et d'écrire la variable locale dans la pile comme comportement par défaut. const registerest la combinaison de ces optimisations enhardies. C'est aussi mince que possible.

De plus, sur gcc C et C ++, registersemble créer à lui seul un écart aléatoire de 16 octets sur la pile pour le premier local de la pile, ce qui ne se produit pas avec const register.

Compiler en utilisant -Ofast cependant; registera un effet d'optimisation nul car s'il peut être mis dans un registre ou rendu immédiat, il le sera toujours et s'il ne le peut pas, il ne le sera pas; constoptimise toujours la charge sur C et C ++ mais au niveau du fichier uniquement ; volatileforce toujours le stockage et le chargement des valeurs à partir de la pile.

.LC0:
  .string "%d"
main:
  //optimises out push and change of rbp
  sub rsp, 8 //https://stackoverflow.com/a/40344912/7194773
  mov esi, 3
  mov edi, OFFSET FLAT:.LC0
  xor eax, eax //xor 2 bytes vs 5 for mov eax, 0
  call printf
  xor eax, eax
  add rsp, 8
  ret
Lewis Kelsey
la source