web-dev-qa-db-fra.com

Déclarer des variables en haut de la fonction ou dans des portées séparées?

Quelle méthode est préférée, méthode 1 ou méthode 2?

Méthode 1:

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
        case WM_Paint:
        {
            HDC hdc;
            PAINTSTRUCT ps;

            RECT rc;
            GetClientRect(hwnd, &rc);           

            hdc = BeginPaint(hwnd, &ps);
            // drawing here
            EndPaint(hwnd, &ps);
            break;
        }
        default: 
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}

Méthode 2:

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rc;

    switch (msg)
    {
        case WM_Paint:
            GetClientRect(hwnd, &rc);

            hdc = BeginPaint(hwnd, &ps);
            // drawing here
            EndPaint(hwnd, &ps);
            break;

        default: 
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}

Dans la méthode 1, si msg = WM_Paint lorsque la fonction wpMainWindow est appelée, la mémoire est-elle allouée pour toutes les variables de la pile au début? ou seulement quand il entre dans la portée de WM_Paint?

La méthode 1 n'utilise-t-elle la mémoire que lorsque le message est WM_Paint et la méthode 2 utilise-t-elle la mémoire, quel que soit le nombre de msg?

34
Kaije

Les variables doivent être déclarées aussi localement que possible.

Déclarer des variables "au sommet de la fonction" est toujours une pratique désastreusement mauvaise. Même en langage C89/90, où les variables ne peuvent être déclarées qu'au début du bloc, il est préférable de les déclarer aussi localement que possible, c'est-à-dire au début du plus petit bloc local couvrant la durée de vie souhaitée de la variable. Parfois, il peut même être judicieux d’introduire un bloc local "redondant" dans le seul but de "localiser" la déclaration de variable.

En C++ et C99, où il est possible de déclarer une variable n'importe où dans le code, la réponse est assez simple: encore une fois, déclarez chaque variable aussi localement que possible et aussi près que possible du point où vous l'avez utilisée pour la première fois. La principale raison en est que, dans la plupart des cas, cela vous permettra de fournir un initialiseur significatif à la variable au moment de la déclaration (au lieu de le déclarer sans initialiseur ou avec un initialiseur factice).

En ce qui concerne l'utilisation de la mémoire, en général, une implémentation typique allouera immédiatement (dès que vous entrez la fonction) l'espace maximal requis pour toutes les variables qui existent au même moment. Cependant, vos habitudes de déclaration peuvent affecter la taille exacte de cet espace. Par exemple, dans ce code

void foo() {
  int a, b, c;

  if (...) {
  }

  if (...) {
  }
}

les trois variables existent en même temps et généralement l'espace doit être alloué aux trois. Mais dans ce code

void foo() {
  int a;

  if (...) {
    int b;
  }

  if (...) {
    int c;
  }
}

il n'y a que deux variables à un moment donné, ce qui signifie qu'un espace pour deux variables seulement sera alloué par une implémentation typique (b et c partageront le même espace). C'est une autre raison de déclarer les variables aussi localement que possible.

55
AnT

Si quelque chose est alloué sur la pile dans le cas 1, l'implémentation est définie. Les implémentations ne sont même pas nécessaires pour avoir une pile.

Il n'est généralement pas plus lent de le faire, car l'opération tend à être une simple soustraction (pour une pile à croissance descendante) d'une valeur du pointeur de pile pour toute la zone de variable locale.

Ce qui est important ici, c'est que la portée soit aussi locale que possible. En d’autres termes, déclarez vos variables le plus tard possible et conservez-les aussi longtemps que nécessaire.

Notez que déclarer ici est à un niveau d'abstraction différent d'allouer de l'espace pour eux. L'espace réel peut être alloué au début de la fonction (niveau d'implémentation), mais vous ne pouvez utiliser ces variables que lorsqu'elles sont définies (niveau C).

La localisation de l'information est importante, tout comme son cousin, l'encapsulation.

12
paxdiablo

J'aime la méthode 3:

LRESULT wpMainWindowPaint(HWND hwnd)
{
    HDC hdc;
    PAINTSTRUCT ps;

    RECT rc;
    GetClientRect(hwnd, &rc);           

    hdc = BeginPaint(hwnd, &ps);
    // drawing here
    EndPaint(hwnd, &ps);
    return 0;
}

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
        case WM_Paint:      return wpMainWindowPaint(hwnd);
        default:            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
}

Si elle mérite sa propre portée pour des raisons d'organisation, elle mérite sa propre fonction. Si vous êtes inquiet à propos de la surcharge d’appel de fonction, mettez-la en ligne.

6
Ben Voigt

Puisqu'il appartient au compilateur d'optimiser mon code, une heure de compilateur est bien moins chère qu'une heure de mon temps, et mon temps est perdu si je dois faire défiler le code pour voir où une variable a été déclarée, Je pense que mon entreprise veut que je garde tout le plus local possible.

Je ne parle même pas du «plus petit bloc», mais «aussi près de l'endroit où il est utilisé»!

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) 
{ 
    switch (msg) 
    { 
        case WM_Paint: 
        { 
            RECT rc; 
            GetClientRect(hwnd, &rc);            

            { // sometimes I even create an arbitrary block 
              // to show correlated statements.
              // as a side-effect, the compiler may not need to allocate space for 
              // variables declared here...
              PAINTSTRUCT ps; 
              HDC hdc = BeginPaint(hwnd, &ps); 
              // drawing here 
              EndPaint(hwnd, &ps); 
            }
            break; 
        } 
        default:  
            return DefWindowProc(hwnd, msg, wparam, lparam); 
    } 
    return 0; 
} 
4
xtofl

Définissez les variables dans la portée la plus étroite où elles sont pertinentes. Il n'y a aucune raison d'utiliser la méthode 2 ci-dessus à mon avis.

L'espace de pile n'est susceptible d'être utilisé que lorsque les variables sont dans la portée. Comme @paxdiablo le fait remarquer, vos sections locales peuvent se retrouver dans des registres plutôt que sur la pile, si le compilateur peut trouver l'espace nécessaire.

3
Steve Townsend

L'allocation de mémoire n'est pas spécifiée dans la norme de manière aussi détaillée. Par conséquent, pour obtenir une réponse réelle, vous devez spécifier le compilateur et la plate-forme. Ce n'est pas important pour la performance.

Ce que vous voulez, c'est la lisibilité. En général, vous devez déclarer les variables dans la plus petite portée utilisable et, de préférence, les initialiser immédiatement avec des valeurs raisonnables. Plus la variable est petite, moins elle peut potentiellement interagir avec le reste du programme de façon imprévisible. Plus la déclaration se rapproche de l'initialisation, moins il est possible que quelque chose de mal se produise.

Quel serait probablement mieux est quelque chose comme

RECT rc;
GetClientRect(hwnd, &rc);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

Ceci est pour C++. Pour C, la règle est similaire, sauf que les versions précédentes de C exigeaient que toutes les variables soient déclarées en haut d'un bloc.

1
David Thornley

Vous ne pouvez pas savoir à quel moment la réservation de pile est faite.

Pour la lisibilité, j'irais avec C99 (ou C++). Cela vous permet de déclarer une variable vraiment là où la première utilisation.

 HDC hdc = BeginPaint(hwnd, &ps);
1
Jens Gustedt

Pour le langage de programmation Java, la pratique courante consiste à déclarer des variables locales uniquement lorsque cela est nécessaire dans une méthode.

void foo(int i) {
  if (i == 1)
    return;
  Map map1 = new HashMap();
  if (i == 2)
    return;
  Map map2 = new HashMap();
}

Pour le langage de programmation C++, je suggère également la même pratique car déclarer des variables avec un constructeur non trivial implique un coût d’exécution. Le fait de mettre toutes ces déclarations au début de la méthode entraîne des coûts inutiles si certaines de ces variables sont utilisées.

void foo(int i) 
{
  if (i == 1)
    return;
  std::map<int, int> map1; // constructor is executed here
  if (i == 2)
    return;
  std::map<int, int> map2; // constructor is executed here
}

Pour C, l'histoire est différente. Cela dépend de l'architecture et du compilateur. Pour x86 et GCC, placer toutes les déclarations au début de la fonction et déclarer des variables uniquement lorsque cela est nécessaire a les mêmes performances. La raison en est que les variables C n'ont pas de constructeur. Et l'effet de ces deux approches sur l'allocation de mémoire en pile est le même. Voici un exemple:

void foo(int i)
{
  int m[50];
  int n[50];
  switch (i) {
    case 0:
      break;
    case 1:
      break;
    default:
      break;
  }
}

void bar(int i) 
{
  int m[50];
  switch (i) {
    case 0:
      break;
    case 1:
      break;
    default:
      break;
  }
  int n[50];
}

Pour les deux fonctions, le code d'assemblage pour la manipulation de pile est:

pushl   %ebp
movl    %esp, %ebp
subl    $400, %esp

Mettre toutes les déclarations au début de la fonction est courant dans le code du noyau Linux.

0
Jingguo Yao

Il n'est pas nécessaire de polluer la pile avec des variables qui ne sont peut-être jamais utilisées. Allouez vos vars juste avant leur utilisation. En négligeant le RECT rc et l'appel suivant à GetClientRect, la méthode de Ben Voight est la voie à suivre.

0
myeviltacos