¿Declarar variables en la parte superior de la función o en ámbitos separados?

¿Cuál es el preferido, método 1 o método 2?

Método 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étodo 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; } 

En el método 1, si msg = WM_PAINT cuando se llama a la función wpMainWindow, ¿asigna memoria para todas las variables en la stack al principio? o solo cuando ingresa al scope WM_PAINT?

¿El método 1 solo usaría la memoria cuando el mensaje es WM_PAINT, y el método 2 usaría la memoria sin importar qué msg igualaba?

Las variables deben declararse lo más localmente posible.

Declarar variables “en la parte superior de la función” es siempre una práctica desastrosamente mala. Incluso en el lenguaje C89 / 90, donde las variables solo se pueden declarar al principio del bloque, es mejor declararlas de la forma más local posible, es decir, al comienzo del bloque local más pequeño que cubre la duración deseada de la variable. A veces, incluso podría tener sentido introducir un bloque local “redundante” con el único propósito de “localizar” la statement de la variable.

En C ++ y C99, donde es posible declarar una variable en cualquier parte del código, la respuesta es bastante sencilla: una vez más, declare cada variable lo más localmente posible y lo más cerca posible del punto donde la usa la primera vez. El motivo principal para eso es que en la mayoría de los casos esto le permitirá suministrar un inicializador significativo a la variable en el punto de statement (en lugar de declararlo sin inicializador o con un inicializador ficticio).

En cuanto al uso de la memoria, en general, una implementación típica asignará de inmediato (al ingresar la función) el espacio máximo requerido para todas las variables que existen al mismo tiempo. Sin embargo, sus hábitos de statement pueden afectar el tamaño exacto de ese espacio. Por ejemplo, en este código

 void foo() { int a, b, c; if (...) { } if (...) { } } 

las tres variables existen al mismo tiempo y, en general, se debe asignar el espacio para las tres. Pero en este código

 void foo() { int a; if (...) { int b; } if (...) { int c; } } 

solo existen dos variables en un momento dado, lo que significa que el espacio para solo dos variables será asignado por una implementación típica ( b compartirán el mismo espacio). Esta es otra razón para declarar las variables lo más localmente posible.

Si algo está asignado en la stack en el caso 1 es la implementación definida. Las implementaciones ni siquiera son necesarias para tener una stack.

Por lo general, no es más lento hacerlo ya que la operación tiende a ser una simple resta (para una stack que crece hacia abajo) de un valor del puntero de stack para el área de variable local completa.

Lo importante aquí es que el scope debe ser lo más local posible. En otras palabras, declare sus variables lo más tarde posible y solo consérvelas el tiempo que sea necesario.

Tenga en cuenta que declarar aquí está en un nivel de abstracción diferente a la asignación de espacio para ellos. El espacio real se puede asignar al inicio de la función (nivel de implementación) pero solo puede usar esas variables mientras están en el scope (nivel C).

La ubicación de la información es importante, al igual que su primo, la encapsulación.

Me gusta el Método 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 merece su propio scope para fines de organización, merece su propia función. Si le preocupa la sobrecarga de llamada de función, hágalo en línea.

Dado que el trabajo del comstackdor es optimizar mi código, y una hora de comstackción es mucho más barata que una hora de mi tiempo, y mi tiempo se desperdicia si necesito desplazarme hacia arriba y hacia abajo para ver dónde se declaró una variable, Creo que mi empresa quiere que mantenga todo lo más local posible.

Ni siquiera estoy hablando de “el bloque más pequeño”, sino “tan cerca del lugar donde se usa”.

 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; } 

Defina las variables en el ámbito más estrecho donde sean relevantes. No hay ninguna razón para usar el Método 2 anterior en mi opinión.

El espacio de stack solo se utilizará cuando las variables estén dentro del scope. Como señala @paxdiablo, tus locales pueden terminar en registros en lugar de en la stack, si el comstackdor puede encontrar el espacio para ellos.

La asignación de memoria no se especifica en el Estándar con este detalle, por lo que para una respuesta real deberá especificar el comstackdor y la plataforma. No va a importar para el rendimiento.

Lo que desea es legibilidad, y en general eso se hace declarando las variables en el ámbito utilizable más pequeño, y preferiblemente cuando puede inicializarlas inmediatamente con valores razonables. Cuanto menor es el scope de una variable, menos puede potencialmente interactuar con el rest del progtwig de maneras impredecibles. Cuanto más cerca esté la statement de la inicialización, menos oportunidades habrá de que algo malo suceda.

Lo que probablemente sería mejor es algo así como

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

Esto es para C ++. Para C, la regla es similar, excepto que las versiones anteriores de C requieren que todas las variables se declaren en la parte superior de un bloque.

No puede saber en qué punto se realiza la reserva de stack.

Para la legibilidad iría con C99 (o C ++). Eso te permite la statement de una variable realmente allí donde primero la usas.

  HDC hdc = BeginPaint(hwnd, &ps); 

No hay necesidad de contaminar la stack con variables que posiblemente nunca se usen. Asigne sus vars justo antes de ser utilizados. Pasando por alto el RECT rc y la posterior llamada a GetClientRect , el método de Ben Voight es el camino a seguir.

Para el lenguaje de progtwigción Java, la práctica común es declarar variables locales solo cuando sea necesario en un método.

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

Para el lenguaje de progtwigción C ++, también sugiero la misma práctica, ya que declarar variables con un constructor no trivial implica un costo de ejecución. Poner todas estas declaraciones al comienzo del método causa un costo innecesario si se usan algunas de estas variables.

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

Para C, la historia es diferente. Depende de la architecture y el comstackdor. Para x86 y GCC, poner todas las declaraciones al comienzo de la función y declarar las variables solo cuando sea necesario tienen el mismo rendimiento. La razón es que las variables C no tienen un constructor. Y el efecto en la asignación de la memoria de la stack por estos dos enfoques es el mismo. Aquí hay un ejemplo:

 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]; } 

Para ambas funciones, el código de ensamblaje para la manipulación de la stack es:

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

Poner todas las declaraciones al principio de la función es común en el código del kernel de Linux.