Que fait le register
mot-clé en langage C? J'ai lu qu'il est utilisé pour l'optimisation mais n'est clairement défini dans aucune norme. Est-il toujours pertinent et si oui, quand l'utiliseriez-vous?
register
variable.
Que fait le register
mot-clé en langage C? J'ai lu qu'il est utilisé pour l'optimisation mais n'est clairement défini dans aucune norme. Est-il toujours pertinent et si oui, quand l'utiliseriez-vous?
register
variable.
Réponses:
C'est une indication pour le compilateur que la variable sera fortement utilisée et que vous recommandez qu'elle soit conservée dans un registre de processeur si possible.
La plupart des compilateurs modernes le font automatiquement et sont meilleurs à les choisir que nous, les humains.
register
variable soit prise; c'est le seul effet obligatoire du register
mot - clé. Même cela est suffisant pour améliorer les optimisations, car il devient trivial de dire que la variable ne peut être modifiée que dans cette fonction.
Je suis surpris que personne n'ait mentionné que vous ne pouvez pas prendre une adresse de variable de registre, même si le compilateur décide de garder la variable en mémoire plutôt que dans le registre.
Donc, en utilisant register
vous ne gagnez rien (de toute façon le compilateur décidera par lui-même où mettre la variable) et perdez l' &
opérateur - aucune raison de l'utiliser.
register
est donc utile pour cela même si le compilateur ne le met pas dans un registre.
const
est également inutile car il ne vous gagne rien, vous perdez seulement la possibilité de changer une variable. register
pourrait être utilisé pour s'assurer que personne ne prendra l'adresse d'une variable à l'avenir sans réfléchir. Je n'ai cependant jamais eu de raison de l'utiliser register
.
Il indique au compilateur d'essayer d'utiliser un registre CPU, au lieu de RAM, pour stocker la variable. Les registres sont dans le CPU et beaucoup plus rapides d'accès que la RAM. Mais ce n'est qu'une suggestion pour le compilateur, et cela peut ne pas suivre.
Je sais que cette question concerne C, mais la même question pour C ++ a été fermée en tant que doublon exact de cette question. Cette réponse peut donc ne pas s'appliquer à C.
Le dernier projet de la norme C ++ 11, N3485 , le dit dans 7.1.1 / 3:
Un
register
spécificateur est un indice de l'implémentation que la variable ainsi déclarée sera largement utilisée. [ note: L'indice peut être ignoré et dans la plupart des implémentations, il sera ignoré si l'adresse de la variable est prise. Cette utilisation est déconseillée ... —fin note ]
En C ++ (mais pas en C), la norme n'indique pas que vous ne pouvez pas prendre l'adresse d'une variable déclarée register
; cependant, comme une variable stockée dans un registre CPU tout au long de sa durée de vie n'a pas d'emplacement mémoire associé, la tentative de prendre son adresse serait invalide et le compilateur ignorera le register
mot clé pour autoriser la prise de l'adresse.
Cela n'a pas été pertinent depuis au moins 15 ans, car les optimiseurs prennent de meilleures décisions à ce sujet que vous ne le pouvez. Même quand c'était pertinent, cela avait beaucoup plus de sens sur une architecture CPU avec beaucoup de registres, comme SPARC ou M68000 que sur Intel avec sa rareté de registres, dont la plupart sont réservés par le compilateur à ses propres fins.
En fait, register indique au compilateur que la variable n'a pas d'alias avec quoi que ce soit d'autre dans le programme (pas même les caractères).
Cela peut être exploité par les compilateurs modernes dans diverses situations et peut aider le compilateur dans un code complexe - dans un code simple, les compilateurs peuvent le comprendre par eux-mêmes.
Sinon, il ne sert à rien et n'est pas utilisé pour l'attribution des registres. Il n'entraîne généralement pas de dégradation des performances pour le spécifier, tant que votre compilateur est suffisamment moderne.
register
interdit de prendre l'adresse du tout, car sinon il pourrait être utile d'informer les compilateurs des cas où un compilateur pourrait appliquer des optimisations de registre malgré le fait que l'adresse d'une variable soit passée à une fonction externe (la variable devrait être vidé en mémoire pour cet appel particulier , mais une fois la fonction de retour, le compilateur pourrait à nouveau la traiter comme une variable dont l'adresse n'a jamais été prise).
bar
est une register
variable, un compilateur peut à sa guise remplacer foo(&bar);
par int temp=bar; foo(&temp); bar=temp;
, mais prendre l'adresse de bar
serait interdit dans la plupart des autres contextes ne semblerait pas être une règle trop compliquée. Si la variable pouvait autrement être conservée dans un registre, la substitution rendrait le code plus petit. Si la variable devait de toute façon être conservée dans la RAM, la substitution augmenterait le code. Laisser la question de l'opportunité de faire la substitution au compilateur conduirait à un meilleur code dans les deux cas.
register
qualification sur des variables globales, que le compilateur permette ou non de prendre l'adresse, permettrait de belles optimisations dans les cas où une fonction en ligne qui utilise une variable globale est appelée à plusieurs reprises dans une boucle. Je ne peux pas penser à une autre façon de laisser cette variable dans un registre entre les itérations de boucle - pouvez-vous?
J'ai lu qu'il est utilisé pour l'optimisation mais n'est clairement défini dans aucune norme.
En fait, il est clairement défini par la norme C. Citant le projet de section 6.7.1 N1570 , paragraphe 6 (les autres versions ont le même libellé):
Une déclaration d'un identifiant pour un objet avec un spécificateur de classe de stockage
register
suggère que l'accès à l'objet soit aussi rapide que possible. La mesure dans laquelle ces suggestions sont efficaces est définie par la mise en œuvre.
L' &
opérateur unaire ne peut pas être appliqué à un objet défini avec register
et register
ne peut pas être utilisé dans une déclaration externe.
Il existe quelques autres règles (assez obscures) spécifiques aux register
objets qualifiés:
register
un comportement indéfini. register
, mais vous ne pouvez rien faire d'utile avec un tel objet (l'indexation dans un tableau nécessite de prendre l'adresse de son élément initial)._Alignas
spécificateur (nouveau dans C11) ne peut pas être appliqué à un tel objet.va_start
macro est register
qualifié, le comportement n'est pas défini.Il peut y en avoir quelques autres; téléchargez un brouillon de la norme et recherchez «s'inscrire» si vous êtes intéressé.
Comme son nom l'indique, la signification d' origine de register
était d'exiger qu'un objet soit stocké dans un registre CPU. Mais avec l'amélioration de l'optimisation des compilateurs, cela est devenu moins utile. Les versions modernes de la norme C ne font pas référence aux registres du processeur, car elles ne supposent plus (ont besoin) qu'il existe une telle chose (il existe des architectures qui n'utilisent pas de registres). La sagesse courante est que l'application register
à une déclaration d'objet est plus susceptible d' aggraver le code généré, car elle interfère avec l'allocation de registre du compilateur. Il peut encore y avoir quelques cas où cela est utile (par exemple, si vous savez vraiment à quelle fréquence une variable sera consultée et que vos connaissances sont meilleures que ce qu'un compilateur d'optimisation moderne peut comprendre).
Le principal effet tangible de cela register
est qu'il empêche toute tentative de prendre l'adresse d'un objet. Cela n'est pas particulièrement utile comme indice d'optimisation, car il ne peut être appliqué qu'aux variables locales, et un compilateur d'optimisation peut constater par lui-même que l'adresse d'un tel objet n'est pas prise.
register
d'objet tableau qualifié, si c'est ce que vous pensez.
register
objets du tableau; voir le premier point mis à jour dans ma réponse. Il est légal de définir un tel objet, mais vous ne pouvez rien en faire. Si vous ajoutez register
à la définition de s
dans votre exemple , le programme est illégal (une violation de contrainte) en C. C ++ ne place pas les mêmes restrictions register
, donc le programme serait C ++ valide (mais l'utilisation register
serait inutile).
register
mot-clé pourrait être utile s'il était légal de prendre l'adresse d'une telle variable, mais uniquement dans les cas où la sémantique ne serait pas affectée en copiant la variable dans un temporaire lorsque son adresse est prise et en la rechargeant à partir du temporaire au point de séquence suivant. Cela permettrait aux compilateurs de supposer que la variable peut être conservée en toute sécurité dans un registre à travers tous les accès de pointeur à condition qu'elle soit vidée à tous les endroits où son adresse est prise.
L'heure du conte!
C, en tant que langage, est une abstraction d'un ordinateur. Il vous permet de faire des choses, en termes de ce que fait un ordinateur, c'est-à-dire manipuler la mémoire, faire des mathématiques, imprimer des choses, etc.
Mais C n'est qu'une abstraction. Et finalement, ce qu'il extrait de vous, c'est le langage d'assemblage. L'assemblage est le langage qu'un CPU lit, et si vous l'utilisez, vous faites les choses en termes de CPU. Que fait un CPU? Fondamentalement, il lit de la mémoire, fait des calculs et écrit dans la mémoire. Le CPU ne fait pas seulement des calculs sur les nombres en mémoire. Tout d'abord, vous devez déplacer un nombre de mémoire en mémoire à l'intérieur du CPU appelé registre. Une fois que vous avez fait tout ce que vous devez faire pour ce numéro, vous pouvez le ramener dans la mémoire système normale. Pourquoi utiliser la mémoire système? Les registres sont limités en nombre. Vous n'obtenez qu'une centaine d'octets dans les processeurs modernes, et les processeurs populaires plus anciens étaient encore plus fantastiquement limités (le 6502 avait 3 registres 8 bits pour votre utilisation gratuite). Ainsi, votre opération mathématique moyenne ressemble à:
load first number from memory
load second number from memory
add the two
store answer into memory
Beaucoup de cela n'est ... pas des mathématiques. Ces opérations de chargement et de stockage peuvent prendre jusqu'à la moitié de votre temps de traitement. C, étant une abstraction d'ordinateurs, a libéré le programmeur du souci d'utiliser et de jongler avec les registres, et puisque le nombre et le type varient selon les ordinateurs, C place la responsabilité de l'allocation des registres uniquement sur le compilateur. À une exception près.
Lorsque vous déclarez une variable register
, vous dites au compilateur "Yo, j'ai l'intention que cette variable soit beaucoup utilisée et / ou soit de courte durée. Si j'étais vous, j'essaierais de la garder dans un registre." Lorsque la norme C dit que les compilateurs n'ont rien à faire, c'est parce que la norme C ne sait pas pour quel ordinateur vous compilez, et cela pourrait être comme le 6502 ci-dessus, où les 3 registres sont nécessaires uniquement pour fonctionner , et il n'y a pas de registre de réserve pour conserver votre numéro. Cependant, quand il dit que vous ne pouvez pas prendre l'adresse, c'est parce que les registres n'ont pas d'adresse. Ce sont les mains du processeur. Étant donné que le compilateur n'a pas à vous donner d'adresse, et comme il ne peut jamais avoir d'adresse du tout, plusieurs optimisations sont désormais ouvertes au compilateur. Il pourrait, par exemple, toujours conserver le numéro dans un registre. C'est pas ca' t avoir à se soucier de l'endroit où il est stocké dans la mémoire de l'ordinateur (au-delà de la nécessité de le récupérer à nouveau). Il pourrait même le punter dans une autre variable, le donner à un autre processeur, lui donner un emplacement changeant, etc.
tl; dr: variables de courte durée qui font beaucoup de calculs. N'en déclarez pas trop à la fois.
Vous jouez avec l'algorithme sophistiqué de coloration des graphes du compilateur. Ceci est utilisé pour l'attribution des registres. Enfin, surtout. Il agit comme un indice pour le compilateur - c'est vrai. Mais pas ignoré dans son intégralité car vous n'êtes pas autorisé à prendre l'adresse d'une variable de registre (rappelez-vous que le compilateur, à votre merci, essaiera d'agir différemment). Ce qui, d'une certaine manière, vous dit de ne pas l'utiliser.
Le mot-clé a été utilisé depuis très longtemps. Quand il y avait seulement si peu de registres qui pouvaient tous les compter avec votre index.
Mais, comme je l'ai dit, obsolète ne signifie pas que vous ne pouvez pas l'utiliser.
Juste une petite démo (sans but réel) pour comparaison: lors de la suppression des register
mots clés avant chaque variable, ce morceau de code prend 3,41 secondes sur mon i7 (GCC), avec register
le même code se termine en 0,7 secondes.
#include <stdio.h>
int main(int argc, char** argv) {
register int numIterations = 20000;
register int i=0;
unsigned long val=0;
for (i; i<numIterations+1; i++)
{
register int j=0;
for (j;j<i;j++)
{
val=j+i;
}
}
printf("%d", val);
return 0;
}
J'ai testé le mot clé register sous QNX 6.5.0 en utilisant le code suivant:
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int main(int argc, char *argv[]) {
uint64_t cps, cycle1, cycle2, ncycles;
double sec;
register int a=0, b = 1, c = 3, i;
cycle1 = ClockCycles();
for(i = 0; i < 100000000; i++)
a = ((a + b + c) * c) / 2;
cycle2 = ClockCycles();
ncycles = cycle2 - cycle1;
printf("%lld cycles elapsed\n", ncycles);
cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
printf("This system has %lld cycles per second\n", cps);
sec = (double)ncycles/cps;
printf("The cycles in seconds is %f\n", sec);
return EXIT_SUCCESS;
}
J'ai obtenu les résultats suivants:
-> 807679611 cycles écoulés
-> Ce système a 3300830000 cycles par seconde
-> Les cycles en secondes sont ~ 0.244600
Et maintenant sans vous inscrire:
int a=0, b = 1, c = 3, i;
J'ai eu:
-> 1421694077 cycles écoulés
-> Ce système a 3300830000 cycles par seconde
-> Le nombre de cycles en secondes est ~ 0.430700
Register informerait le compilateur que le codeur pensait que cette variable serait suffisamment écrite / lue pour justifier son stockage dans l'un des rares registres disponibles pour une utilisation variable. La lecture / écriture à partir des registres est généralement plus rapide et peut nécessiter un ensemble de codes d'opération plus petit.
De nos jours, cela n'est pas très utile, car la plupart des optimiseurs de compilateurs sont meilleurs que vous pour déterminer si un registre doit être utilisé pour cette variable et pour combien de temps.
Au cours des années soixante-dix, au tout début du langage C, le mot-clé register a été introduit afin de permettre au programmeur de donner des conseils au compilateur, lui disant que la variable serait utilisée très souvent, et qu'il devrait être judicieux de conserver sa valeur dans l'un des registres internes du processeur.
De nos jours, les optimiseurs sont beaucoup plus efficaces que les programmeurs pour déterminer les variables qui sont plus susceptibles d'être conservées dans les registres, et l'optimiseur ne prend pas toujours en compte l'indication du programmeur.
Tant de gens recommandent à tort de ne pas utiliser le mot-clé d'enregistrement.
Voyons pourquoi!
Le mot-clé de registre a un effet secondaire associé: vous ne pouvez pas référencer (obtenir l'adresse de) une variable de type de registre.
Les personnes conseillant aux autres de ne pas utiliser les registres prennent à tort cela comme un argument supplémentaire.
Cependant, le simple fait de savoir que vous ne pouvez pas prendre l'adresse d'une variable de registre, permet au compilateur (et à son optimiseur) de savoir que la valeur de cette variable ne peut pas être modifiée indirectement via un pointeur.
Lorsqu'à un certain point du flux d'instructions, une variable de registre a sa valeur affectée dans le registre d'un processeur et que le registre n'a pas été utilisé depuis pour obtenir la valeur d'une autre variable, le compilateur sait qu'il n'a pas besoin de recharger la valeur de la variable dans ce registre. Cela permet d'éviter un accès mémoire coûteux et inutile.
Faites vos propres tests et vous obtiendrez des améliorations de performances significatives dans vos boucles les plus internes.
Le compilateur Visual C ++ de Microsoft ignore le register
mot clé lorsque l'optimisation globale d'allocation de registre (l'indicateur de compilateur / Oe) est activée.
Voir enregistrer un mot-clé sur MSDN.
Le mot-clé Register indique au compilateur de stocker la variable particulière dans les registres CPU afin qu'elle puisse être accessible rapidement. Du point de vue d'un programmeur, le mot-clé de registre est utilisé pour les variables qui sont fortement utilisées dans un programme, afin que le compilateur puisse accélérer le code. Bien que cela dépende du compilateur de conserver la variable dans les registres CPU ou la mémoire principale.
Register indique au compilateur d'optimiser ce code en stockant cette variable particulière dans des registres puis en mémoire. c'est une demande au compilateur, le compilateur peut ou non considérer cette demande. Vous pouvez utiliser cette fonction au cas où certaines de vos variables sont consultées très fréquemment. Par exemple: une boucle.
Une autre chose est que si vous déclarez une variable comme registre, vous ne pouvez pas obtenir son adresse car elle n'est pas stockée en mémoire. il obtient son allocation dans le registre CPU.
sortie asm de gcc 9.3, sans utiliser de drapeaux d'optimisation (tout dans cette réponse fait référence à une compilation standard sans drapeaux d'optimisation):
#include <stdio.h>
int main(void) {
int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 3
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
#include <stdio.h>
int main(void) {
register int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
mov ebx, 3
add ebx, 1
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add rsp, 8
pop rbx
pop rbp
ret
Cela force ebx
à être utilisé pour le calcul, ce qui signifie qu'il doit être poussé vers la pile et restauré à la fin de la fonction car il est sauvegardé. register
produit plus de lignes de code et 1 écriture en mémoire et 1 lecture en mémoire (bien qu'en réalité, cela aurait pu être optimisé à 0 R / W si le calcul avait été fait en esi
, ce qui se passe en utilisant C ++ const register
). Le fait de ne pas utiliser register
entraîne 2 écritures et 1 lecture (bien que le stockage pour charger le transfert se produise lors de la lecture). En effet, la valeur doit être présente et mise à jour directement sur la pile afin que la valeur correcte puisse être lue par adresse (pointeur). register
n'a pas cette exigence et ne peut pas être pointé. const
et register
sont fondamentalement l'opposé de volatile
et en utilisantvolatile
remplacera les optimisations const à la portée du fichier et du bloc et les register
optimisations à la portée du bloc. const register
et register
produira des sorties identiques parce que const ne fait rien sur C à l'étendue du bloc, donc seules les register
optimisations s'appliquent.
Sur clang, register
est ignoré mais des const
optimisations se produisent toujours.