Question

Lors de la compilation des codes de C / C en utilisant gcc / g ++, si elle ne tient pas compte de mon registre, peut-il me dire? Par exemple, dans ce code

int main()
{
    register int j;
    int k;
    for(k = 0; k < 1000; k++)
        for(j = 0; j < 32000; j++)
            ;
    return 0;
}

j sera utilisé comme registre, mais dans ce code

int main()
{
    register int j;
    int k;
    for(k = 0; k < 1000; k++)
        for(j = 0; j < 32000; j++)
            ;
    int * a = &j;
    return 0;
}

j sera une variable normale. Peut-il me dire si une variable je registre est vraiment stocké dans un registre CPU?

Était-ce utile?

La solution

Vous pouvez raisonnablement présumer que GCC ignore le mot-clé register sauf peut-être à -O0. Cependant, il ne devrait pas faire un moyen d'une différence ou d'une autre, et si vous êtes dans une telle profondeur, vous devriez déjà lire le code assembleur.

Voici un fil d'information sur ce sujet: http: // gcc.gnu.org/ml/gcc/2010-05/msg00098.html. Retour dans les temps anciens, register en effet aidé les compilateurs d'allouer une variable dans des registres, mais l'allocation de registre d'aujourd'hui peut être accompli de manière optimale, automatiquement, sans conseils. Le mot-clé ne continue à deux objectifs en C:

  1. En C, il vous empêche de prendre l'adresse d'une variable. Étant donné que les registres ne sont pas les adresses, cette restriction peut aider un compilateur C simple. (Simple compilateurs C ++ n'existent pas.)
  2. Un objet register ne peut pas être déclaré restrict. Parce que rapporte la restrict aux adresses, leur intersection est inutile. (C ++ n'a pas encore restrict, et de toute façon, cette règle est un peu trivial.)

Pour C ++, le mot-clé a été dépréciée depuis C ++ 11 et proposé pour le retrait de la révision de la norme prévue pour 2017.

Certains compilateurs ont utilisé register sur les déclarations de paramètres pour déterminer la convention d'appel de fonctions, avec l'ABI permettant stack- mixte et les paramètres à partir des registres. Cela semble être non conforme, il a tendance à se produire avec la syntaxe étendue comme register("A1"), et je ne sais pas si un tel compilateur est encore utilisé.

Autres conseils

En ce qui concerne les techniques de compilation modernes et d'optimisation, l'annotation register ne fait pas de sens du tout. Dans votre deuxième programme que vous prenez l'adresse de j, et les registres n'ont pas les adresses, mais une même variable locale ou statique pourrait très bien être stockés dans deux emplacements de mémoire différents au cours de sa vie, ou parfois en mémoire et parfois dans un registre, ou ne pas exister du tout. En effet, un compilateur optimisant compilerait vos boucles imbriquées comme rien, parce qu'ils n'ont pas d'effets, et affecter simplement leurs valeurs finales à k et j. Et puis omettre ces affectations parce que le reste du code n'utilise pas ces valeurs.

Vous ne pouvez pas obtenir l'adresse d'un registre en C, ainsi que le compilateur peut ignorer totalement vous; norme C99, section 6.7.1 (pdf) :

  

La mise en œuvre peut traiter tout   enregistrer une déclaration simplement comme une auto   déclaration. Cependant, si oui ou non   stockage adressable est effectivement utilisé,   l'adresse d'une partie d'un objet   a déclaré avec spécificateur-classe de stockage   registre ne peut être calculé, soit   explicitement (par l'utilisation de la unaire &   opérateur comme décrit dans 6.5.3.2) ou   implicitement (par la conversion d'un groupement   nom à un pointeur comme indiqué dans   6.3.2.1). Ainsi, le seul opérateur qui peut être appliquée à un tableau déclaré   avec registre de spécification de classe de stockage   est sizeof.

À moins que vous bidouiller sur AVRs ou PICs 8 bits, le compilateur va probablement rire de vous en pensant que vous connaissez le mieux et ne pas tenir compte de vos moyens. Même sur eux, j'ai pensé que je connaissais mieux une ou deux fois et trouvé des façons de tromper le compilateur (avec quelques asm en ligne), mais mon code a explosé car il fallait masser un tas d'autres données au travail autour de mon opiniâtreté.

Cette question, et quelques-unes des réponses, et plusieurs autres discussions des mots-clés 'inscrire J'ai vu - semble supposer implicitement que toutes les sections locales sont mises en correspondance soit à un registre spécifique, ou à un emplacement mémoire spécifique sur la pile. Cela était généralement vrai jusqu'à il y a 15-25 ans, et il est vrai si vous désactivez l'optimisation, mais il est pas vrai du tout Lors de l'optimisation standard est exécutée. Les sections locales sont désormais considérés par optimiseurs comme des noms symboliques que vous utilisez pour décrire le flux de données, plutôt que comme des valeurs qui doivent être stockés dans des endroits spécifiques.

Note: par des « locaux » ici que je veux dire: les variables scalaires, de l'auto de classe de stockage (ou « registre »), qui ne sont jamais utilisés comme opérateur de « & ». Compilateurs peut parfois briser struct auto, des syndicats ou des tableaux en variables individuelles « locales », aussi.

Pour illustrer ceci: supposons que j'écris ceci en haut d'une fonction:

int factor = 8;

.. et l'utilisation uniquement de la variable factor est de multiplier par diverses choses:

arr[i + factor*j] = arr[i - factor*k];

Dans ce cas - l'essayer si vous voulez - il n'y aura pas de variable factor. L'analyse de code montrera que factor est toujours 8, et donc tous les quarts de travail se transformera en <<3. Si vous avez fait la même chose en 1985 C, factor obtiendrait un emplacement sur la pile, et il y aurait multipilies, puisque les producteurs essentiellement travaillé une déclaration à la fois et ne se souvenait de rien sur les valeurs des variables. À l'époque, les programmeurs seraient plus susceptibles d'utiliser #define factor 8 pour obtenir un meilleur code dans cette situation, tout en maintenant factor réglable.

Si vous utilisez -O0 (arrêt d'optimisation) - vous en effet obtenir une variable pour factor. Cela vous permettra, par exemple, enjamber la déclaration de factor=8, puis le changement factor à 11 avec le débogueur, et continuez. Pour que cela fonctionne, le compilateur ne peut pas garder quoi que ce soit dans les registres entre les déclarations, à l'exception des variables qui sont affectées à des registres spécifiques; et dans ce cas, le débogueur est informé. Et il ne peut pas essayer de tout « savoir » sur les valeurs des variables, car le débogueur pourrait les changer. En d'autres termes, vous devez la situation 1985 si vous voulez modifier les variables locales pendant le débogage.

compilateurs modernes compile généralement une fonction comme suit:

(1) quand une variable locale est affecté à plus d'une fois dans une fonction, le compilateur crée différentes « versions » de la variable de telle sorte que chacun est affecté uniquement à un seul endroit. Toutes les « lit » de la variable se réfèrent à une version spécifique.

(2) Chacun de ces locaux est affecté à un registre « virtuel ». les résultats des calculs intermédiaires sont également variables assignées / registres; si

  a = b*c + 2*k;

devient quelque chose comme

       t1 = b*c;
       t2 = 2;
       t3 = k*t2;
       a = t1 + t3;

(3) Le compilateur prend alors toutes ces opérations, et cherche des sous-expressions communes, etc. Comme chacun des nouveaux registres n'est jamais écrit une fois, il est assez facile de les réorganiser tout en maintenant la décision correcte. Je vais même pas commencer à l'analyse de la boucle.

(4) Le compilateur essaie ensuite de cartographier tous ces registres virtuels dans des registres réels, afin de générer le code. Étant donné que chaque registre virtuel a une durée de vie limitée, il est possible de réutiliser les registres réels fortement - « t1 » dans ce qui précède est uniquement nécessaire jusqu'à ce que le module complémentaire qui génère « a », il pourrait se tenir dans le même registre que « a ». Quand il n'y a pas assez de registres, certains des registres virtuels peuvent être attribués à la mémoire - ou - une valeur peut être tenue dans un certain registre, stocké dans la mémoire pendant un certain temps, et le dos chargé dans un (peut-être) plus tard registre différent . Sur une machine de charge magasin, où seules les valeurs dans les registres peuvent être utilisés dans les calculs, cette deuxième stratégie qui bien accueille.

De ce qui précède, il devrait être clair: il est facilepour déterminer que le registre virtuel mappé à factor est le même que la constante « 8 », et ainsi tous les multiplications par factor sont multiplications par 8. Même si factor est modifié plus tard, qui est une variable « nouvelle » et il ne porte pas atteinte avant utilisations de factor.

Une autre conséquence, si vous écrivez

 vara = varb;

.. il peut ou ne peut pas être le cas qu'il ya une copie correspondante dans le code. Par exemple

int *resultp= ...
int acc = arr[0] + arr[1];
int acc0 = acc;    // save this for later
int more = func(resultp,3)+ func(resultp,-3);
acc += more;         // add some more stuff
if( ...){
    resultp = getptr();
    resultp[0] = acc0;
    resultp[1] = acc;
}

Dans ce qui précède les deux « versions » de acc (initiale et après l'ajout de « plus ») pourrait être dans deux registres différents, et « ACC0 » serait alors le même que le inital 'acc. Donc, aucune copie de registre serait nécessaire pour 'ACC0 = acc. Un autre point:. Le « resultp » est attribué à deux fois, et depuis la deuxième affectation ne tient pas compte de la valeur précédente, il y a essentiellement deux variables « de resultp » distinctes dans le code, ce qui est facilement déterminée par l'analyse

Une implication de tout cela: ne pas hésiter à sortir des expressions complexes en plus petites en utilisant les locaux supplémentaires pour les intermédiaires, si elle rend le code plus facile à suivre. Il est pratiquement nul peine d'exécution pour cela, puisque l'optimiseur voit la même chose de toute façon.

Si vous souhaitez en savoir plus, vous pouvez commencer ici: http://en.wikipedia.org / wiki / Static_single_assignment_form

Le point de cette réponse est (a) donner une idée de la façon dont le travail des compilateurs modernes et (b) soulignent que demander au compilateur, si ce serait si gentil, de mettre une variable locale particulière dans un registre - ne fait pas vraiment de sens. Chaque « variable » peut être vu par l'optimiseur comme plusieurs variables, dont certaines peuvent être fortement utilisés dans les boucles, et d'autres non. Certaines variables disparaîtront - par exemple en étant constant; ou, parfois, la variable de température utilisée dans un swap. Ou calculs pas réellement utilisés. Le compilateur est équipé d'utiliser le même registre pour différentes choses dans différentes parties du code, selon ce qui est en fait le mieux sur la machine que vous compilez pour.

La notion de laissant entendre le compilateur à des variables qui doivent être dans des registres suppose que chaque variable locale correspond à un registre ou à un emplacement de mémoire. Cela était vrai en arrière quand Kernighan + Ritchie a conçu le langage C, mais il est pas vrai plus.

En ce qui concerne la restriction que vous ne pouvez pas prendre l'adresse d'une variable de registre: De toute évidence, il n'y a aucun moyen de mettre en œuvre en prenant l'adresse d'une variable tenue dans un registre, mais vous pouvez demander - puisque le compilateur a le pouvoir discrétionnaire d'ignorer la « registre » - pourquoi cette règle en place? Pourquoi ne peut pas le compilateur simplement ignorer le « registre » si j'arrive de prendre l'adresse? (Comme cela est le cas en C ++).

Encore une fois, il faut revenir à l'ancien compilateur. Le compilateur d'origine K + R serait analyser une déclaration variable locale, puis immédiatement décider de l'assigner à un registre ou non (et si oui, quel registre). Ensuite, il procéderait à des expressions compilation, en émettant l'assembleur pour chacun des états à la fois. Si elle a trouvé plus tard que vous prenez l'adresse d'une variable « registre », qui avait été affecté à un registre, il n'y avait aucun moyen de gérer cela, étant donné que la cession était, en général, irréversible alors. Cependant, il était possible, pour générer un message d'erreur et la compilation d'arrêt.

En bout de ligne, il semble que « registre » est essentiellement obsolète:

  • compilateurs C de l'ignorer complètement
  • compilateurs C l'ignorent, sauf pour appliquer la restriction sur les & - et peut-être ne l'ignorez pas à -O0 où il pourrait effectivement donner lieu à l'attribution à la demande. À -O0 vous n'êtes pas préoccupé par la vitesse de code bien.

Alors, il est fondamentalement là maintenant pour la compatibilité ascendante, et probablement sur la base que certaines implémentations COUld toujours l'utiliser pour « conseils ». Je ne l'utilise - et j'écrire du code DSP en temps réel, et de passer un peu de temps juste de regarder le code généré et trouver des moyens de le rendre plus rapide. Il y a plusieurs façons de modifier le code pour faire tourner plus vite, et de savoir comment compilateurs travail est très utile. Cela fait longtemps en effet depuis que je trouve que l'ajout de « registre » pour être parmi ces moyens.


Addendum

J'exclus ci-dessus, de ma définition spéciale des « locaux », variables auxquelles & est appliquée (ceux-ci sont sont bien sûr inclus dans le sens habituel du terme).

Considérons le code ci-dessous:

void
somefunc()
{
    int h,w;
    int i,j;
    extern int pitch;

    get_hw( &h,&w );  // get shape of array

    for( int i = 0; i < h; i++ ){
        for( int j = 0; j < w; j++ ){
            Arr[i*pitch + j] = generate_func(i,j);
        }
    }
}

Cela peut sembler tout à fait inoffensif. Mais si vous êtes préoccupé par la vitesse d'exécution, considérez ceci: Le compilateur passe les adresses de h et w à get_hw, puis appeler plus tard generate_func. Supposons que le compilateur ne sait rien de ce qui est dans ces fonctions (ce qui est le cas général). Le compilateur doit supposer que l'appel à generate_func pourrait changer h ou w. C'est une utilisation parfaitement légale du pointeur est passé à get_hw -., Vous pouvez le stocker quelque part et ensuite l'utiliser plus tard, tant que le champ contenant h,w est encore en jeu, de lire ou d'écrire ces variables

Ainsi, le must du compilateur magasin h et w en mémoire sur la pile, et ne peut rien déterminer à l'avance combien de temps la boucle sera exécutée. Ainsi, certaines optimisations seront impossibles, et la boucle pourrait être moins efficace en raison (dans cet exemple, il y a de toute façon, il ne peut pas faire un appel de fonction dans la boucle interne une grande différence, mais considérer le cas où il y a une fonction qui est occasionnellement appelé dans la boucle interne, en fonction de certaines conditions).

Une autre question est que generate_func pourrait changer pitch, et si les besoins de i*pitch à faire à chaque fois, plutôt que lorsque les changements de i.

Il peut être recodé comme:

void
somefunc()
{
    int h0,w0;
    int h,w;
    int i,j;
    extern int pitch;
    int apit = pitch;

    get_hw( &h0,&w0 );  // get shape of array
    h= h0;
    w= w0;

    for( int i = 0; i < h; i++ ){
        for( int j = 0; j < w; j++ ){
            Arr[i*apit + j] = generate_func(i,j);
        }
    }
}

Maintenant, les variables sont apit,h,w toutes les sections locales « sûres » dans le sens que je défini ci-dessus, et le compilateur peut être sûr qu'ils ne seront pas modifiées par des appels de fonction. En supposant que je suis pas modifier quoi que ce soit dans generate_func, le code aura le même effet que précédemment, mais pourrait être plus efficace.

Jens Gustedt a suggéré l'utilisation du mot « registre » comme un moyen de marquage variables clés pour inhiber l'utilisation des & sur eux, par exemple, par d'autres en maintenant le code (Il n'affectera pas le code généré, car le compilateur peut déterminer l'absence de & sans lui). Pour ma part, je pense toujours attentivement avant d'appliquer & à un scalaire local dans une zone critique du temps du code, et à mon avis en utilisant « registre » pour faire respecter c'est un peu cryptique, mais je peux voir le point (malheureusement, il ne fonctionne pas en C ++ puisque le compilateur simplement ignorer le « registre »).

Par ailleurs, en termes d'efficacité du code, la meilleure façon d'avoir un retour de la fonction deux valeurs est une struct:

struct hw {  // this is what get_hw returns
   int h,w;
};

void
somefunc()
{
    int h,w;
    int i,j;

    struct hw hwval = get_hw();  // get shape of array
    h = hwval.h;
    w = hwval.w;
    ...

peut sembler lourd (et est lourd à écrire), mais il va générer le code plus propre que les exemples précédents. Le « struct hw » seront effectivement retournés dans deux registres (sur la plupart ABIs modernes de toute façon). Et en raison de la façon dont le « hwval » struct est utilisé, l'optimiseur effectivement diviser en deux la hwval.h et hwval.w 'habitants de, puis de déterminer que ceux-ci sont équivalentes à h et w - donc hwval va essentiellement disparaître dans le code . Aucun pointeur doit être passé, aucune fonction modifie variables d'une autre fonction par pointeur; il est juste comme avoir deux valeurs de retour scalaires distinctes. Cela est beaucoup plus facile à fairemaintenant en C ++ 11 -. avec std::tie et std::tuple, vous pouvez utiliser cette méthode avec moins verbosité (et sans avoir à écrire une définition struct)

Votre deuxième exemple est invalide en C. Donc, vous voyez bien que le mot-clé register change quelque chose (en C). Il est juste là à cet effet, d'inhiber la prise d'une adresse d'une variable. Il suffit donc de ne pas prendre son nom « registre » verbalement, il est un abus de langage, mais le bâton à sa définition.

Ce C ++ semble ignorer register, bien qu'ils doivent avoir leur raison pour cela, mais je trouve un peu triste de retrouver l'une de ces différences subtiles où un code valide pour une est valide pour l'autre.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top