Que fait le mot clé register
en langage C? J'ai lu qu'il est utilisé pour l'optimisation mais qu'il n'est défini clairement dans aucune norme. Est-il toujours d'actualité et si oui, quand l'utiliseriez-vous?
C'est un indice pour le compilateur que la variable sera fortement utilisée et que vous recommandez de la conserver si possible dans un registre de processeur.
La plupart des compilateurs modernes le font automatiquement et sont mieux à même de les choisir que nous, les humains.
Je suis surpris que personne ne dise que vous ne pouvez pas prendre une adresse de variable de registre, même si le compilateur décide de conserver la variable en mémoire plutôt que dans le registre.
Donc, en utilisant register
vous ne gagnez rien (le compilateur décidera lui-même où placer la variable) et vous perdrez l'opérateur &
- aucune raison de l'utiliser.
Il indique au compilateur d'essayer d'utiliser un registre de CPU, au lieu de RAM, pour stocker la variable. Les registres sont dans la CPU et sont beaucoup plus rapides d'accès que la RAM. Mais ce n'est qu'une suggestion au compilateur, et cela peut ne pas aboutir.
Je sais que cette question concerne le C, mais la même question pour C++ a été fermée en tant que duplicata exact de cette question. Cette réponse peut donc ne pas s'appliquer à C.
La dernière version du standard C++ 11, N3485 , dit ceci en 7.1.1/3:
Un spécificateur
register
indique à l'implémentation que la variable ainsi déclarée sera fortement utilisée. [note: Le conseil 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 ... - end 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, étant donné qu'une variable stockée dans un registre de la CPU tout au long de sa vie ne possède pas d'emplacement mémoire associé, toute tentative de prise de son adresse serait invalide et le compilateur ignorera le mot clé register
pour autoriser la saisie de l'adresse.
Cela n'a pas été pertinent depuis au moins 15 ans, car les optimiseurs prennent de meilleures décisions que vous ne pouvez le faire. Même lorsque cela était pertinent, cela était beaucoup plus logique sur une architecture de 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.
J'ai lu qu'il est utilisé pour l'optimisation mais qu'il n'est défini clairement dans aucune norme.
En fait, il est clairement défini par le standard C. Citant le N1570 draft section 6.7.1 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 objets qualifiés register
-:
register
a un comportement indéfini.register
, mais vous ne pouvez rien faire d'utile avec un tel objet (indexer dans un tableau nécessite de prendre l'adresse de son élément initial)._Alignas
(nouveauté de C11) ne peut pas être appliqué à un tel objet.va_start
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 "enregistrer" si cela vous intéresse.
Comme son nom l'indique, original == signification de register
nécessitait qu'un objet soit stocké dans un registre de la CPU. Mais avec des améliorations dans l'optimisation des compilateurs, cela est devenu moins utile. Les versions modernes de la norme C ne font pas référence aux registres de la CPU, car elles ne supposent plus qu'il en soit ainsi (il existe des architectures qui n'utilisent pas de registres). Il est généralement admis que l’application de register
à une déclaration d’objet a plus de chances de empire le code généré, car il interfère avec l’allocation de registres propre du compilateur. Il peut encore rester quelques cas utiles (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).
L'effet tangible principal de register
est qu'il empêche toute tentative de saisie de l'adresse d'un objet. Ceci n'est pas particulièrement utile en tant qu'indicateur d'optimisation, car il ne peut être appliqué qu'à des variables locales et un compilateur optimiseur peut voir par lui-même que l'adresse d'un tel objet n'est pas prise.
En fait, register indique au compilateur que la variable ne fait aucun 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 une variété de situations et peut aider le compilateur dans un code complexe - dans un code simple, les compilateurs peuvent le résoudre eux-mêmes.
Sinon, il ne sert à rien et n'est pas utilisé pour l'allocation de registre. Cela n'entraîne généralement pas de dégradation des performances pour le spécifier, tant que votre compilateur est suffisamment moderne.
L'heure du conte!
C, en tant que langage, est une abstraction d'un ordinateur. Cela vous permet de faire des choses, du point de vue d'un ordinateur, comme manipuler la mémoire, faire des calculs, imprimer des choses, etc.
Mais C n'est qu'une abstraction. Et finalement, ce dont il s’extrait vous est le langage d’assemblage. Assembly est le langage lu par une CPU et, si vous l'utilisez, vous agissez en termes de CPU. Que fait un processeur? Fondamentalement, il lit dans la mémoire, fait des mathématiques et écrit dans la mémoire. Le processeur ne fait pas que calculer les chiffres en mémoire. Tout d'abord, vous devez déplacer un numéro de mémoire en mémoire à l'intérieur de la CPU, appelé registre. Une fois que vous avez terminé de faire tout ce que vous avez besoin de faire pour ce numéro, vous pouvez le remettre dans la mémoire système normale. Pourquoi utiliser la mémoire système? Les registres sont en nombre limité. Vous ne disposez que d'une centaine d'octets dans les processeurs modernes, et les processeurs populaires les plus anciens étaient encore plus limités (le 6502 disposait de 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
C'est en grande partie ... pas des maths. Ces opérations de chargement et de stockage peuvent prendre jusqu'à la moitié de votre temps de traitement. C étant une abstraction des ordinateurs, le programmeur n'a plus à se soucier d'utiliser et de jongler avec les registres. Etant donné que le nombre et le type varient d'un ordinateur à l'autre, C attribue la responsabilité de l'attribution des registres uniquement au compilateur. À une exception près.
Lorsque vous déclarez une variable register
, vous dites au compilateur "Yo, j’ai l’intention de beaucoup utiliser cette variable et/ou d’être de courte durée. Si j’étais vous-même, j’essaierais de la conserver dans un S'inscrire." Quand le standard C dit que les compilateurs ne doivent rien faire, c'est parce que le standard C ne sait pas pour quel ordinateur vous compilez, et cela pourrait ressembler au 6502 ci-dessus, où les 3 registres sont nécessaires pour fonctionner , et il n’ya pas de registre disponible pour garder votre numéro. Cependant, quand il est 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 une adresse et qu'il ne peut pas en avoir du tout, plusieurs optimisations sont maintenant ouvertes pour le compilateur. Il pourrait, par exemple, conserver le numéro dans un registre toujours. Il n'a pas à s'inquiéter de l'endroit où il est stocké dans la mémoire de l'ordinateur (au-delà du besoin de le récupérer). Il pourrait même y mettre une autre variable, la donner à un autre processeur, lui donner un emplacement différent, etc.
tl; dr: Variables de courte durée faisant beaucoup de maths. Ne déclarez pas trop à la fois.
Juste une petite démo (sans but réel) pour comparaison: lors de la suppression des mots-clés register
avant chaque variable, ce morceau de code prend 3,41 secondes sur mon i7 (GCC), avecregister
le même code se termine en 0,7 seconde.
#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;
}
Vous jouez avec l'algorithme sophistiqué de coloration des graphes du compilateur. Ceci est utilisé pour l'allocation de registre. Eh bien, surtout. Cela agit comme un indice pour le compilateur - c'est vrai. Mais pas ignoré dans son intégralité puisque vous n'êtes pas autorisé à prendre l'adresse d'une variable de registre (souvenez-vous que le compilateur, maintenant à votre merci, essaiera d'agir différemment). Ce qui, en un sens, vous dit de ne pas l'utiliser.
Le mot clé a été utilisé très longtemps. Quand il y avait si peu de registres qui pouvaient tous les compter en utilisant votre index.
Mais, comme je l'ai dit, obsolète ne signifie pas que vous ne pouvez pas l'utiliser.
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 eu 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 inscription int:
int a=0, b = 1, c = 3, i;
J'ai eu:
-> 1421694077 cycles écoulés
-> Ce système a 3300830000 cycles par seconde
-> Les cycles en secondes sont ~ 0.430700
Dans les 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 indications au compilateur, en lui indiquant que la variable serait utilisée très souvent et qu'il conserve 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 les plus susceptibles d’être conservées dans des registres, et l’optimiseur ne tient pas toujours compte des indications du programmeur.
Tant de personnes recommandent à tort de ne pas utiliser le mot clé register.
Voyons pourquoi!
Le mot clé register a un effet secondaire associé: vous ne pouvez pas référencer (obtenir l'adresse de) une variable de type registre.
Les personnes qui conseillent aux autres de ne pas utiliser les registres prennent à tort cela comme 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.
Lorsque, à un certain point du flux d'instructions, une variable de registre se voit attribuer sa valeur dans le registre d'un processeur et que le registre n'a pas été utilisé car, pour obtenir la valeur d'une autre variable, le compilateur sait qu'il n'est pas nécessaire de le recharger. la valeur de la variable dans ce registre. Cela permet d’éviter des accès coûteux en mémoire inutiles.
Faites vos propres tests et vous obtiendrez des améliorations de performances significatives dans vos boucles les plus internes.
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 de registres est généralement plus rapide et peut nécessiter un jeu de codes d'opération plus petit.
De nos jours, cela n’est plus très utile, car les optimiseurs de la plupart des compilateurs sont plus aptes que vous à déterminer si un registre doit être utilisé pour cette variable et pendant combien de temps.
Le mot clé Register indique au compilateur de stocker la variable dans des registres de la CPU afin de la rendre accessible rapidement. Du point de vue du programmeur, le mot clé register 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 de la CPU ou dans la mémoire principale.
Le compilateur Visual C++ de Microsoft ignore le mot clé register
lorsque l'optimisation globale de l'attribution de registre (l'indicateur de compilateur/Oe) est activée.
Voir register Keyword sur MSDN.
Sur les compilateurs C pris en charge, il essaie d'optimiser le code afin que la valeur de la variable soit conservée dans un registre de processeur réel.
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 si certaines de vos variables sont utilisées très fréquemment. Par exemple: une boucle.
Une autre chose est que si vous déclarez une variable en tant que 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 de la CPU.