JTH hat geschrieben: 04.09.2021 12:13:40
Meillo hat geschrieben: 03.09.2021 17:17:18
Wie dem auch sei. Mit meinem ``ich glaube, ich uebersehe etwas'' meine ich den Code von dakuan. Ich hatte nicht verstanden, welchen Grund der Compiler hat, diesen Fehler zu werfen.
[…]
Es gibt keinen sprachlichen Grund dafuer (was eben meine Frage war), […]
Doch, den gibt es tatsächlich. Es ist in C++, aber nicht in C, einfach ausdrücklich verboten, in den Gültigkeitsbereich einer Variablen über deren Deklaration hinweg hineinzuspringen:
Verbirgt sich im ISO-Standard anscheinend im Abschnitt zu
Statements / Declaration statement. Da finde ich es aber nicht so leicht verständlich. Ein Hoch auf die Dokumentationen auf cppreference.com, die sind immer sehr hilfreich!
Vielen Dank fuer diese Info! ... und auch ueberhaupt fuer deinen ausfuehrlichen Post und die Recherche.
(Zur Erklaerung: Ich programmiere nur C. Mir war nicht bewusst, dass es auf dieser tiefen Ebene auch solche Unterschiede gibt.)
Zu:
JTH hat geschrieben: 04.09.2021 12:13:40
Meillo hat geschrieben: 03.09.2021 17:17:18
[…] sondern das ist nur dem erzieherischen Element der Compilerentwickler geschuldet, das zunehmend um sich greift, wie mir scheint.
Ich finds mit Blick auf die Fehler durch uninitialisierte Variablen, die es mit C und C++ ja oft gibt, nicht verkehrt, dass so eine Problemquelle hier ausdrücklich nicht nur durch die Compilerentwickler, sondern sogar durch den Sprachstandard ausgeschlossen ist.
Eine – logische – Folge der Regel: Der Compilefehler kommt nur, wenn man mehr als einen Case hat. Denn mit nur einem Case gibt es letztendlich keinen Sprung. Folgendes kompiliert als C++:
Mit einem zusätzlichen Case scheitert es dann wieder wie bekannt.
Und:
JTH hat geschrieben: 04.09.2021 12:13:40
Meillo hat geschrieben: 03.09.2021 15:51:54
dakuan hat geschrieben: 03.09.2021 15:22:25
Ich denke, der Compiler macht sich das sehr einfach, indem er erstmal alles, was innerhalb des switch() Blocks als ein Scope sieht.
Das ist nunmal das Design von Switch in C/C++ ...
Durch die Tatsache, dass der switch-Block ein eigener Scope ist, lassen sich anscheinend lustige Dinge damit machen: Man kann eine Variable
vor dem ersten Case deklarieren:
Compiliert erfolgreich als C und C++. Man kann dort allerdings keinen Wert zuweisen,
die Zeilen vor dem Case werden nie ausgeführt. Daher würd ich das nicht in ernsthaftem Code benutzen. Ich denke, es verwirrt die meisten Codeleser mehr, als dass es hilft.
Diese zwei Beispiele, die du hier anfuehrst, zeigen aus meiner Sicht, dass es zwar eine nette Idee ist, dieses Problem mit dem Sprachstandard loesen zu wollen, sich dies damit in der Realitaet aber damit gar nicht erreichen laesst. Das Design von Switch laesst es nicht zu, dass man hier formal ansetzen kann, weil das Switch dafuer nicht strukturiert genug ist. Man kann es dann so sehen, dass es gut ist, wenn man wenigstens die haeufigsten Faelle abfaengt, oder man kann es schlecht finden, dass man den Sprachstandard aufblaeht ohne dass man das Problem damit wirklich loesen kann. Fuer mich wirkt es unstimmig, mit so harten Mitteln ein Problem anzugehen, das so weich ist. Ich finde, dass man die (aus heutiger Sicht dominierenden) Schwaechen des Switch und seines Design akzeptieren muss, statt sich schoenzureden, dass es anders waere oder man das formal reparieren koennte ohne sein Design zu aendern. Wenn man es richtig haette loesen wollen, dann haette man ein anderes Switch gebraucht. Aber nun gut, diese Zerrissenheit, zwischen C-Kompatibilitaet wahren und gegen dessen unerwuenschte Nachteile anzuarbeiten, sind ein Grund warum ich kein C++ programmiere.
Wenn man diese ganzen potenziellen Problemfaelle Warnings umsetzt, dann finde ich das sinnvoll. Wenn man daraus harte Fehler oder halblebige Loesungen im Sprachstandard macht, finde ich das weniger gut. Dein Beispiel zeigt ja schoen, wie man sich weiterhin in den Fuss schiessen kann ... waehrend der Sprachstandard in dem einen (noch nicht mal zwangslaeufig falschen) Fall den Eindruck erweckt, dass man die Probleme formal eingedaemmt haette. Das ist eine Unstimmigkeit, die ich nicht mag, weil ich dann nicht einschaetzen kann was ich zu erwarten habe. Ich werde immer wieder ueberrascht werden weil die Sprache eben unstimmig ist. Darum mag ich C so sehr: Dort weiss ich genau was ich zu erwarten habe, weil C eine so grosse Stimmigkeit im Sprachdesign hat.
JTH hat geschrieben: 04.09.2021 12:13:40
Meillo hat geschrieben: 03.09.2021 17:17:18
Hier ein Testprogramm, das zwar C-Code enthaelt aber mit g++ uebersetzt wird:
Wenn du den Code mit dem g++ kompilierst, ist es in dem Moment doch C++-Code und wird mit entsprechenden Regeln wie der obigen übersetzt. Gültig ist er ja in beiden Sprachen.
Damit hast du recht.
JTH hat geschrieben: 04.09.2021 12:13:40
Als C-Code würde dein (noch weiter) vereinfachtest Beispiel mit Überspringen der Variableninitialisierung auch entsprechend ohne obige C++-Regel erfolgreich kompilieren:
Aber da man mit dem Überspringen der Initialisierung undefiniertes Verhalten hat (ist das tatsächlich eins?), macht der Compiler hier beliebiges und unerwartetes draus und aus der Funktion z.B. das Äquivalent eines
return 0, siehe verlinkten Assembleroutput.
Hier machst du einen Fehler weil dein Beispiel *keine* Vereinfachung meines Beispiels ist, sondern einen ganz anderen Fall darstellt, bei dem ich den Fehler auch problemlos nachvollziehen kann. Dein Beispiel muss zurecht angemeckert werden, weil `x' im Return nicht bekannt sein kann, mein Beispiel und dekuans Code haben dieses Problem aber nicht. Sie sind aber von folgendem Schema:
Code: Alles auswählen
int foo()
{
int y = 1;
goto my_label;
int x = 7;
y = x;
my_label:
return y;
}
D.h. es gibt keine Zugriffe auf Variablen deren Definition nicht erfolgt ist. Wuerde man den uebersprungenen Code entfernen, dann waere der Rest valide. Schaut man sich den uebersprungenen Code fuer sich genommen an, dann ist er ebenfalls valide.
Dein Beispiel muss vom Compiler abgelehnt werden, weil der Code nicht funktionieren kann; mein Beispiel kann kompiliert werden weil der Code durchaus problemlos funktioniert. (Einen Hinweis auf das *potenzielle* aber nicht zwangslaeufige Problem faende ich schon sinnvoll, aber das ist nebensaechlich.)
Hier nochmal meine Versuche zur Demonstration:
Code: Alles auswählen
:-Q cat c.c
#include <stdio.h>
int main(void)
{
int y = 4;
goto my_label;
int x = 7;
y = x;
my_label:
return y;
}
:-Q c99 -Wall -Wextra -pedantic c.c -o c
:-Q ./c ; echo $?
4
:-Q rm c
:-Q g++ -Wall -Wextra -pedantic c.c -o c
c.c: In function ‘int main()’:
c.c:9:1: error: jump to label ‘my_label’ [-fpermissive]
c.c:6:10: error: from here [-fpermissive]
c.c:7:9: error: crosses initialization of ‘int x’
:-Q ./c ; echo $?
mksh: ./c: not found
127
:-Q