webentwicklung-frage-antwort-db.com.de

Variablen oben in der Funktion oder in separaten Bereichen deklarieren?

Was ist bevorzugt, Methode 1 oder Methode 2?

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

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

Wenn in Methode 1 beim Aufrufen der Funktion wpMainWindow msg = WM_Paint ist, wird zu Beginn Speicher für alle Variablen auf dem Stapel reserviert? oder erst, wenn es in den WM_Paint-Bereich eintritt?

Würde Methode 1 nur den Speicher verwenden, wenn die Nachricht WM_Paint ist, und Methode 2 würde den Speicher verwenden, unabhängig davon, welche Nachricht gleich ist?

34
Kaije

Variablen sollten so lokal wie möglich deklariert werden.

Das Deklarieren von Variablen "ganz oben auf der Funktion" ist immer eine katastrophale schlechte Praxis. Selbst in der Sprache C89/90, in der Variablen nur am Anfang des Blocks deklariert werden können, ist es besser, sie so lokal wie möglich zu deklarieren, d. H. Am Anfang des kleinsten lokalen Blocks, der die gewünschte Lebensdauer der Variablen abdeckt. Manchmal kann es sogar sinnvoll sein, einen "redundanten" lokalen Block einzuführen, um die Variablendeklaration zu "lokalisieren".

In C++ und C99, wo es möglich ist, Variablen an einer beliebigen Stelle im Code zu deklarieren, ist die Antwort ziemlich einfach: Deklarieren Sie jede Variable so lokal wie möglich und so nah wie möglich an dem Punkt, an dem Sie sie zum ersten Mal verwenden. Der Hauptgrund dafür ist, dass Sie in den meisten Fällen einen aussagekräftigen Initialisierer für die Variable zum Zeitpunkt der Deklaration angeben können (anstatt sie ohne Initialisierer oder mit einem Dummy-Initialisierer zu deklarieren).

In Bezug auf die Speichernutzung weist eine typische Implementierung im Allgemeinen sofort (bei Eingabe der Funktion) den maximal erforderlichen Speicherplatz für alle Variablen zu, die gleichzeitig vorhanden sind. Ihre Deklarationsgewohnheiten können sich jedoch auf die genaue Größe dieses Bereichs auswirken. Zum Beispiel in diesem Code

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

  if (...) {
  }

  if (...) {
  }
}

alle drei Variablen existieren gleichzeitig und in der Regel muss der Platz für alle drei zugewiesen werden. Aber in diesem Code

void foo() {
  int a;

  if (...) {
    int b;
  }

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

zu einem bestimmten Zeitpunkt sind nur zwei Variablen vorhanden, was bedeutet, dass bei einer typischen Implementierung nur Speicherplatz für zwei Variablen zugewiesen wird (b und c teilen sich denselben Speicherplatz). Dies ist ein weiterer Grund, Variablen so lokal wie möglich zu deklarieren.

55
AnT

Ob etwas auf dem Stapel in Fall 1 zugeordnet ist, ist durch die Implementierung definiert. Implementierungen sind nicht einmal erforderlich, um einen Stack zu haben.

Es ist normalerweise kein langsamer), da die Operation in der Regel eine einfache Subtraktion (für einen nach unten wachsenden Stapel) eines Werts vom Stapelzeiger für den gesamten lokalen Variablenbereich ist.

Wichtig hierbei ist, dass der Geltungsbereich so lokal wie möglich ist. Mit anderen Worten, deklarieren Sie Ihre Variablen so spät wie möglich und bewahren Sie sie nur so lange wie nötig auf.

Beachten Sie, dass sich die Deklaration hier auf einer anderen Abstraktionsebene befindet als die Zuweisung von Speicherplatz für sie. Der tatsächliche Speicherplatz kann zu Beginn der Funktion zugewiesen werden (Implementierungsstufe), Sie können diese Variablen jedoch nur verwenden, während der Gültigkeitsbereich festgelegt ist (Stufe C).

Die Lokalität von Informationen ist ebenso wichtig wie die Verkapselung.

12
paxdiablo

Ich mag Methode 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);
    }
}

Wenn es seinen eigenen Gestaltungsspielraum verdient, verdient es seine eigene Funktion. Wenn Sie sich Sorgen über den Funktionsaufruf-Overhead machen, machen Sie ihn inline.

6
Ben Voigt

Da es Aufgabe des Compilers ist, meinen Code zu optimieren, und eine Stunde Compiler-Zeit weitaus billiger ist als eine Stunde meiner Zeit, und meine Zeit wird verschwendet, wenn ich im Code nach oben und unten scrollen muss, um zu sehen, wo eine Variable deklariert wurde. Ich glaube, meine Firma möchte, dass ich alles so lokal wie möglich halte.

Ich spreche nicht einmal von 'dem kleinsten Block', sondern 'so nahe an dem Ort, an dem er benutzt wird'!

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

Definieren Sie die Variablen im engsten Bereich, wo sie relevant sind. Meiner Meinung nach gibt es keinen Grund, Methode 2 zu verwenden.

Stapelspeicher wird wahrscheinlich nur verwendet, wenn sich die Variablen im Gültigkeitsbereich befinden. Wie @paxdiablo hervorhebt, werden Ihre Einheimischen möglicherweise in Registern und nicht auf dem Stapel abgelegt, wenn der Compiler den Platz für sie findet.

3
Steve Townsend

Die Speicherzuordnung ist im Standard für dieses Detail nicht angegeben, sodass Sie für eine echte Antwort Compiler und Plattform angeben müssen. Es wird keine Rolle für die Leistung spielen.

Was Sie wollen, ist Lesbarkeit. Dies geschieht im Allgemeinen, indem Sie Variablen im kleinsten verwendbaren Bereich deklarieren, und vorzugsweise, wenn Sie sie sofort mit vernünftigen Werten initialisieren können. Je kleiner der Gültigkeitsbereich einer Variablen ist, desto weniger kann sie möglicherweise auf unvorhersehbare Weise mit dem Rest des Programms interagieren. Je näher die Deklaration an der Initialisierung liegt, desto geringer ist die Chance, dass etwas Schlimmes passiert.

Was wohl besser wäre ist sowas

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

Dies ist für C++. Für C ist die Regel ähnlich, mit der Ausnahme, dass in früheren Versionen von C alle Variablen am Anfang eines Blocks deklariert werden mussten.

1
David Thornley

Sie können nicht wissen, wann die Stapelreservierung abgeschlossen ist.

Aus Gründen der Lesbarkeit würde ich mit C99 (oder C++) gehen. So können Sie eine Variable dort deklarieren, wo Sie sie zum ersten Mal verwenden.

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

In der Java-Programmiersprache werden lokale Variablen in der Regel nur deklariert, wenn dies in einer Methode erforderlich ist.

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

Für die Programmiersprache C++ empfehle ich dieselbe Vorgehensweise, da das Deklarieren von Variablen mit einem nicht trivialen Konstruktor mit Ausführungskosten verbunden ist. Wenn alle diese Deklarationen am Anfang der Methode stehen, entstehen unnötige Kosten, wenn einige dieser Variablen verwendet werden.

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
}

Für C ist die Geschichte anders. Es hängt von der Architektur und dem Compiler ab. Bei x86 und GCC haben das Platzieren aller Deklarationen am Funktionsanfang und das Deklarieren von Variablen nur bei Bedarf die gleiche Leistung. Der Grund dafür ist, dass C-Variablen keinen Konstruktor haben. Die Auswirkungen dieser beiden Ansätze auf die Stapelspeicherzuweisung sind gleich. Hier ist ein Beispiel:

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

Für beide Funktionen lautet der Assembly-Code für die Stapelmanipulation:

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

Das Setzen aller Deklarationen am Funktionsanfang ist im Linux-Kernel-Code üblich.

0
Jingguo Yao

Es ist nicht erforderlich, den Stapel mit Variablen zu belasten, die möglicherweise nie verwendet werden. Ordnen Sie Ihre Vars direkt vor der Verwendung zu. Mit Blick auf den RECT rc und den anschließenden Aufruf von GetClientRect ist Ben Voights Methode der richtige Weg.

0
myeviltacos