Was ist die idiomatische Verwendung von beliebigen Blöcken in C?


15

Ein Block ist eine Liste von Anweisungen, die ausgeführt werden sollen. Beispiele für das Auftauchen von Blöcken in C sind Anweisungen nach einer Weile und if-Anweisungen

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

Mit C kann ein Block auch in einem Block verschachtelt werden. Ich kann dies verwenden, um Variablennamen wiederzuverwenden. Angenommen, ich mag 'x' wirklich.

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

druckt die Nummer 5 zehnmal. Ich denke, ich könnte Situationen sehen, in denen es wünschenswert ist, die Anzahl der Variablennamen niedrig zu halten. Vielleicht in Makro-Erweiterung. Ich kann jedoch keinen schwerwiegenden Grund dafür erkennen, diese Funktion zu benötigen. Kann mir jemand helfen, die Verwendung dieser Funktion zu verstehen, indem er einige Redewendungen angibt, wo sie verwendet wird?


1
Ehrlich gesagt versuche ich nur die C-Syntax zu verstehen und bin neugierig.
Jonathan Gallagher

Der Geist von C ist es, dem Programmierer zu vertrauen. Der Programmierer hat die Macht, etwas Großartiges zu machen ... oder etwas Schreckliches zu machen. Persönlich mag ich nicht überladene Variablennamen, aber ein anderer Programmierer kann. Am Ende des Tages, wenn wir mit minimalen Fehlern erreichen, was wir erreichen sollen ... warum sollten wir uns streiten?
Fiddling Bits

1
Das ist das Gefühl, das ich von C. bekomme. Ich bin alles für Syntax, die verschiedene Codierungsstile unterstützt (solange die Semantik der Sprache in Ordnung ist). Es ist nur so ... Ich habe das gesehen und meine unmittelbare Antwort war, dass ich eine Source-to-Source-Transformation anwenden und alle Variablen in einem Block mit neuen Namen umbenennen und den Block vollständig reduzieren konnte. Jedes Mal, wenn ich denke, ich könnte etwas loswerden, nehme ich an, dass es etwas gibt, das ich verpasst habe.
Jonathan Gallagher

Witziges Timing, als Nicht-C-Programmierer bin ich heute im C-Code auf diese Syntax gestoßen und war gespannt, wofür sie gedacht war. Ich bin froh, dass du gefragt hast.
Brandon

Antworten:


8

Die Idee ist nicht, die Anzahl der Variablennamen gering zu halten oder auf andere Weise die Wiederverwendung von Namen zu fördern, sondern den Bereich der Variablen einzuschränken. Wenn Sie haben:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

dann ist der Umfang von yauf den Block beschränkt, was bedeutet, dass Sie ihn entweder vor oder nach dem Block vergessen können. Dies wird am häufigsten in Verbindung mit Schleifen und Bedingungen verwendet. Sie sehen es auch häufiger in C-ähnlichen Sprachen wie C ++, wo eine Variable, die den Gültigkeitsbereich verlässt, zerstört wird.


Ich denke, Blockierungen treten natürlich bei Bedingungen, Schleifen und Funktionskörpern auf. Ich bin gespannt, dass ich in C irgendwo einen Block platzieren kann. Ihr zweiter Kommentar zu C ++ ist interessant - das Verlassen eines Gültigkeitsbereichs führt zur Zerstörung. Bezieht sich dies nur auf die Speicherbereinigung, oder ist dies die folgende andere Verwendung: Kann ich einen Funktionskörper mit unterschiedlichen "Abschnitten" verwenden und den Speicherbedarf mithilfe von Blöcken steuern, indem die Zerstörung des variablen Speicherbereichs ausgelöst wird?
Jonathan Gallagher

1
Soweit mir bekannt ist, wird C den gesamten Speicher für alle Variablen in der Funktion vorbelegen. Wenn sie den Gültigkeitsbereich auf halbem Weg durch eine Funktion verlassen, ergeben sich in C keine Leistungsvorteile. Ebenso ändert sich der Speicherbedarf nicht, wenn Variablen "nur manchmal" in den Gültigkeitsbereich eintreten, während die Funktion
aufgerufen wird

@Gankro Dies kann Auswirkungen haben, wenn mehrere exklusive verschachtelte Bereiche vorhanden sind. Ein Compiler kann einen eindeutigen vorab zugewiesenen Speicherbereich für Variablen in jedem dieser Bereiche wiederverwenden. Der Grund, warum es nicht in den Sinn kommt, ist, dass, wenn ein einzelner beliebiger Bereich bereits ein Zeichen ist, das Sie wahrscheinlich in eine Funktion extrahieren müssen, zwei oder mehr beliebige Bereiche definitiv ein gutes Indiz dafür sind, dass Sie umgestalten müssen. Trotzdem taucht es von Zeit zu Zeit als vernünftige Lösung auf, wenn beispielsweise Schaltergehäuseblöcke verwendet werden.
1.

16

Denn in früheren Tagen von C konnten neue Variablen nur in einem neuen Block deklariert werden.

Auf diese Weise können Programmierer mitten in einer Funktion neue Variablen einfügen, ohne diese zu verlieren und die Stapelverwendung zu minimieren.

Mit heutigen Optimierern ist dies nutzlos und ein Zeichen dafür, dass Sie in Betracht ziehen müssen, den Block in seiner eigenen Funktion zu extrahieren.

In einer switch-Anweisung ist es hilfreich, die Fälle in eigene Blöcke einzuschließen, um eine doppelte Deklaration zu vermeiden.

In C ++ ist es beispielsweise sehr nützlich für RAII-Sperrwächter und um sicherzustellen, dass die Destruktoren die Freigabesperre ausführen, wenn die Ausführung den Gültigkeitsbereich verlässt und andere Aufgaben außerhalb des kritischen Abschnitts ausgeführt werden.


2
+1 für die RAII Lock Guards. Könnte dasselbe Konzept jedoch nicht auch für die Freigabe großer Stapelspeicher in Teilen einer Routine nützlich sein? Ich habe es noch nie gemacht, aber es klingt wie etwas, das definitiv in einem eingebetteten Code vorkommen könnte ...
J Trana

2

Ich würde es nicht als "willkürliche" Blöcke betrachten. Es ist keine Funktion, die für Entwickler so wichtig ist, aber die Art und Weise, wie C Blöcke verwendet, ermöglicht die Verwendung desselben Blockkonstrukts an mehreren Stellen mit derselben Semantik. Ein Block (in C) ist ein neuer Gültigkeitsbereich, und Variablen, die ihn verlassen, werden entfernt. Dies ist unabhängig von der Verwendung des Blocks einheitlich.

In anderen Sprachen ist dies nicht der Fall. Dies hat den Vorteil, weniger Missbrauch zuzulassen, wie Sie zeigen, aber den Nachteil, dass sich Blöcke je nach Kontext unterschiedlich verhalten.

Ich habe selten eigenständige Blöcke gesehen, die in C oder C ++ verwendet wurden - oft, wenn es eine große Struktur oder ein Objekt gibt, das eine Verbindung oder etwas darstellt, dessen Zerstörung Sie erzwingen möchten. In der Regel ist dies ein Hinweis darauf, dass Ihre Funktion zu viele Aufgaben ausführt und / oder zu lang ist.


1
Leider sehe ich regelmäßig eigenständige Blöcke - in fast allen Fällen, weil die Funktion zu viel und / oder zu lang ist.
Mattnz

Ich sehe und schreibe auch regelmäßig eigenständige Blöcke in C ++, aber nur, um die Zerstörung von Wrappern um Sperren und andere gemeinsam genutzte Ressourcen zu erzwingen.
J Trana

2

Man muss erkennen, dass Programmierprinzipien, die jetzt offensichtlich zu sein scheinen, nicht immer so waren. C Best Practices hängen stark davon ab, wie alt Ihre Beispiele sind. Als C zum ersten Mal eingeführt wurde, galt es als zu ineffizient, Ihren Code in kleine Funktionen zu unterteilen. Dennis Ritchie log im Grunde genommen und sagte, dass Funktionsaufrufe in C wirklich effizient waren (sie waren nicht zu der Zeit), was die Leute dazu brachte, sie mehr zu benutzen, obwohl C-Programmierer irgendwie nie wirklich an einer Kultur der vorzeitigen Optimierung vorbei kamen.

Es ist auch heute noch eine gute Programmierpraxis, den Umfang Ihrer Variablen so gering wie möglich zu halten. Heutzutage tun wir dies in der Regel durch das Erstellen einer neuen Funktion. Wenn Funktionen jedoch als teuer eingestuft werden, ist das Einfügen eines neuen Blocks eine logische Möglichkeit, Ihren Gültigkeitsbereich ohne den Aufwand eines Funktionsaufrufs einzuschränken.

Ich habe jedoch vor über 20 Jahren mit dem Programmieren in C begonnen, als Sie alle Variablen an der Spitze eines Bereichs deklarieren mussten, und ich erinnere mich nicht, dass Variablenschattierungen wie diese jemals als guter Stil angesehen wurden. Wie in einer switch-Anweisung in zwei Blöcken nacheinander neu deklarieren, ja, aber nicht schattieren. Vielleicht, wenn die Variable bereits verwendet wurde und der spezifische Name für die API, die Sie aufrufen, wie zum Beispiel destund srcin strcpy, sehr idiomatisch war .


1

Willkürliche Blöcke sind nützlich, um Zwischenvariablen einzuführen, die nur in speziellen Fällen einer Berechnung verwendet werden.

Dies ist ein gängiges Muster im wissenschaftlichen Rechnen, bei dem numerische Verfahren typischerweise:

  1. sich auf viele Parameter oder Zwischengrößen verlassen;
  2. müssen sich mit vielen Sonderfällen auseinandersetzen.

Aufgrund des zweiten Punktes ist es sinnvoll, temporäre Variablen mit begrenztem Gültigkeitsbereich einzuführen, was durch die Verwendung eines beliebigen Blocks oder durch die Einführung einer Hilfsfunktion erreicht wird.

Während die Einführung einer Hilfsfunktion wie ein Kinderspiel oder eine bewährte Methode aussieht, die man blindlings befolgen sollte, hat dies in dieser speziellen Situation nur geringe Vorteile .

Da es viele Parameter und Zwischengrößen gibt, wollen wir eine Struktur einführen, um diese an die Hilfsfunktion weiterzugeben.

Da wir jedoch konsequent mit unseren Praktiken umgehen wollen, werden wir nicht nur eine Hilfsfunktion einführen, sondern mehrere. Ob wir also Ad-hoc-Strukturen einführen, die Parameter für jede Funktion übermitteln, die viel Code-Overhead zum Hin- und Herbewegen von Parametern verursachen, oder ob wir eine Struktur für alle Arbeitsblätter einführen , die alle unsere Variablen enthält, aber so aussieht Ein Grabpack von Bits ohne Konsistenz, bei dem zu jeder Zeit nur die Hälfte der Parameter eine interessante Bedeutung hat.

Daher sind diese Hilfsstrukturen in der Regel umständlich und erfordern die Wahl zwischen Code-Bloat oder die Einführung einer Abstraktion, deren Umfang zu weit gefasst ist, und schwächen die Bedeutung des Programms, anstatt es zu erweitern .

Die Einführung von Hilfsfunktionen könnte das Unit-Testen des Programms vereinfachen, indem eine feinere Testgranularität eingeführt wird. Die Kombination von Unit-Testing für nicht so einfache Prozeduren und Regressionstests in Form von Vergleichen (mit einer bestimmten Anzahl von numerischen Spuren der Prozeduren) ist jedoch ebenso gut .


0

Im Allgemeinen führt die Wiederverwendung von Variablennamen auf diese Weise zu zu großer Verwirrung bei zukünftigen Lesern Ihres Codes. Es ist besser, die innere Variable einfach etwas anderes zu nennen. Tatsächlich verbietet die C # -Sprache die Verwendung von Variablen auf diese Weise.

Ich konnte sehen, wie die Verwendung von verschachtelten Blöcken in Makro-Erweiterungen nützlich wäre, um Kollisionen bei der Benennung von Variablen zu verhindern.


0

Es ist für das Scoping von Dingen gedacht, die auf dieser Ebene normalerweise keinen Gültigkeitsbereich haben. Dies ist äußerst selten und im Allgemeinen ist Refactoring eine bessere Wahl, aber ich bin in der Vergangenheit ein- oder zweimal in switch-Anweisungen darauf gestoßen:

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

Wenn Sie Wartungsarbeiten in Betracht ziehen, sollten Sie beim Refactoring alle Implementierungen in einer Reihe haben, sofern jede nur ein paar Zeilen lang ist. Es kann einfacher sein, sie alle zusammen zu haben, als eine Liste von Funktionsnamen.

Wenn ich mich recht erinnere, handelte es sich bei den meisten Fällen um geringfügige Abweichungen desselben Codes - mit der Ausnahme, dass einer mit einem anderen identisch war, nur mit zusätzlicher Vorverarbeitung. Ein Schalter stellte die einfachste Form für den Code dar, und der zusätzliche Gültigkeitsbereich ermöglichte die Verwendung zusätzlicher lokaler Variablen, um die er sich und weitere Fälle nicht kümmern mussten (z. B. Variablennamen, die sich versehentlich mit denen überschneiden, die vor dem Schalter definiert wurden, aber später verwendet).

Beachten Sie, dass die switch-Anweisung einfach die Verwendung ist, auf die ich gestoßen bin. Ich bin mir sicher, dass es auch anderswo angewendet werden kann, aber ich kann mich nicht erinnern, dass sie so effektiv eingesetzt wurden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.