jeweils den Fehler "../src/main.c:48: error: expected expression before
'uint8_t'". Wenn ich aber direkt nach dem : eine Funktion aufrufe, oder
auch nur ein einzelnes Semikolon schreibe, dann kompiliert das Prog ohne
Fehler/Warnungen. Das Problem lässt sich auch durch das Einpacken des
case-Blocks (case 1: {...}) beheben. Google hat mir dazu z.B. sowas
gegeben: http://www.thescripts.com/forum/thread663830.html
Anscheinend ist das hier aber etwas anderes, da ich den Fehler durch das
Semikolon umgehen kann. Ausserdem ändert es nichts, wenn ich die
Definition von der Deklaration trenne.
Kann mir jemand sagen, was hier das Problem ist? Vielleicht ein Bug im
GCC?
Es hat übrigens auch nix mit dem IO-Register als Switch-Variable zu tun.
Danke und Gruss,
Philipp
Hängt das nicht damit zusammen, dass Variablen immer nur am Anfang eines
Blockes definiert werden dürfen? (Zumindest nach einem der (alten?)
C-Standards)
Wobei das auch keinen Sinn macht - er erwartet ja etwas VOR der
Variablendeklaration.
Ich nehme an, es ist schon etwas in der Richtung, doch das erklärt
nicht, warum es denn mit einer (leeren) Anweisung davor funktioniert.
@Rufus t. Firefly:
Hast du vorhin etwas gepostet? Ich habe 'ne Mail bekommen, dass du auf
den Beitrag geantwortet hast, doch da ist nix...
Habe mir eben die C99-Syntax nochmal angeguckt. Nach einem Label
(also auch nach einem case-Label) muss ein statement stehen.
Eine Deklaration ist kein statement. Innerhalb eines Blocks
(also innerhalb geschweifter Klammern) dürfen ansonsten statements
und declarations beliebig gemischt werden (das nennt sich dann ein
"block-item" in der Syntaxbeschreibung).
Du musst es also als eigenen Block schreiben:
1
switch(OCR1A){
2
case1:
3
{
4
uint8_tblub=7;
5
usart_send_var("blub",blub);
6
break;
7
}
8
}
Aus dem gleichen Grund darf am Ende kein einzelner Label stehen,
also z. B.
1
switch(OCR1A){
2
case1:...break;
3
case2:...break;
4
default:
5
}
ist unzulässig, wird aber meiner Erinnerung nach vom GCC als language
extension toleriert.
Achso, besten Dank.
Aber kannst du mir auch sagen, warum das so ist? Irgendwie sehe ich den
Sinn davon nicht so recht (Ich bin ja auch kein Compilerbauer).
Karl heinz Buchegger wrote:
> Weil in C ein case nun mal keinen Block definiert.
Nö, das zählt nicht. ;-) In C99 darf man ja statements und
declarations mischen, da musst du auch auf die Eindeutigkeit
der Namen innerhalb ihres jeweiligen Scopes achten.
Da int a in allen auf die Deklaration/Definition folgenden Zeilen des
aktuellen Blocks bekannt ist, ist int a also auch nach dem Label "case
1" bekannt und die Zuweisung zulässig. Auch ohne "Fall-Through".
Da aber dieses Label angesprungen werden kann, ohne daß der auf das
Label "case 0" folgende Code ausgeführt wird, der sich um die
Deklaration/Definition und Initialisierung von int a kümmert, ist int a
zum Zeitpunkt des Anspringen von "case 1" bels nicht bekannt und
zumindestens nicht initialisiert.
Jörg Wunsch wrote:
> Karl heinz Buchegger wrote:>>> Weil in C ein case nun mal keinen Block definiert.>> Nö, das zählt nicht. ;-) In C99 darf man ja statements und> declarations mischen, da musst du auch auf die Eindeutigkeit> der Namen innerhalb ihres jeweiligen Scopes achten.
Auch wieder wahr.
Im Moment ist die Scope Regel ja ziemlich simpel:
Wird ein } erreicht, dann endet ein Scope.
Nimmst du jetzt noch Spezialregeln für case dazu, dann
führst du zusätzliche Scope Regeln ein, die normalerweise
niemand wirklich braucht.
Der scope endet, wenn
* ein } erreicht wird
* ein anderer case erreicht wird
* ein default erreicht wird
Eine ursprünglioch einfache Regel wird dadurch unnötig
kompliziert, ohne dass es dem Programmierer irgendetwas
bringt (das bischen Schreibaufwand für die { } zähle
ich mal nicht). Ich denke mal, davor ist man zurückgeschreckt.
Ach wäre man blos beim return in non-void Funktionen auch
so konsequent gewesen :-)
Der switch-case ist in C sowieso schon ein abartiger Bastard,
der seltsame Eigenschaften hat. Ich sag nur: Duffs Device.
Philipp Burch wrote:
> Ja, das finde ich ja auch logisch, aber warum ist es denn plötzlich> gültig, wenn da noch eine andere Anweisung davor ist?
Hmm. Interessant.
Das kann ich nicht erklären und ich denke auch nicht, dass
das so im C-Standard beabsichtigt ist.
Ich denke mal das ist ein Bug.
Mich verwundert, dass es so funktioniert, nicht, dass es ohne ';' nicht
funktioniert.
@Alle:
Interessant ist, was er aus meinem kleinen Testprogramm macht (Ohne
Optimierungen):
1
volatileinta=3;
2
volatileintb=7;
3
4
intmain(){
5
switch(a){
6
case1:
7
;
8
intx=7;
9
b=x;
10
break;
11
case2:
12
;
13
inty=3;
14
y=x;
15
case3:
16
b=y;
17
break;
18
default:
19
b=0;
20
}
21
22
for(;;);
23
}
1
4: int main() {
2
+0000002F: 93CF PUSH R28 Push register on stack
3
+00000030: 93DF PUSH R29 Push register on stack
4
+00000031: B7CD IN R28,0x3D In from I/O location
5
+00000032: B7DE IN R29,0x3E In from I/O location
6
+00000033: 9726 SBIW R28,0x06 Subtract immediate from word
7
+00000034: B60F IN R0,0x3F In from I/O location
8
+00000035: 94F8 CLI Global Interrupt Disable
9
+00000036: BFDE OUT 0x3E,R29 Out to I/O location
10
+00000037: BE0F OUT 0x3F,R0 Out to I/O location
11
+00000038: BFCD OUT 0x3D,R28 Out to I/O location
12
5: switch (a) {
13
+00000039: 91800060 LDS R24,0x0060 Load direct from data space
14
+0000003B: 91900061 LDS R25,0x0061 Load direct from data space
15
+0000003D: 839E STD Y+6,R25 Store indirect with displacement
16
+0000003E: 838D STD Y+5,R24 Store indirect with displacement
17
+0000003F: 818D LDD R24,Y+5 Load indirect with displacement
18
+00000040: 819E LDD R25,Y+6 Load indirect with displacement
19
+00000041: 3082 CPI R24,0x02 Compare with immediate
20
+00000042: 0591 CPC R25,R1 Compare with carry
21
+00000043: F0B1 BREQ PC+0x17 Branch if equal
22
+00000044: 818D LDD R24,Y+5 Load indirect with displacement
23
+00000045: 819E LDD R25,Y+6 Load indirect with displacement
24
+00000046: 3083 CPI R24,0x03 Compare with immediate
25
+00000047: 0591 CPC R25,R1 Compare with carry
26
+00000048: F0C9 BREQ PC+0x1A Branch if equal
27
+00000049: 818D LDD R24,Y+5 Load indirect with displacement
28
+0000004A: 819E LDD R25,Y+6 Load indirect with displacement
29
+0000004B: 3081 CPI R24,0x01 Compare with immediate
30
+0000004C: 0591 CPC R25,R1 Compare with carry
31
+0000004D: F009 BREQ PC+0x02 Branch if equal
32
+0000004E: C01A RJMP PC+0x001B Relative jump
33
8: int x = 7;
34
+0000004F: E087 LDI R24,0x07 Load immediate
35
+00000050: E090 LDI R25,0x00 Load immediate
36
+00000051: 839C STD Y+4,R25 Store indirect with displacement
37
+00000052: 838B STD Y+3,R24 Store indirect with displacement
38
9: b = x;
39
+00000053: 818B LDD R24,Y+3 Load indirect with displacement
40
+00000054: 819C LDD R25,Y+4 Load indirect with displacement
41
+00000055: 93900063 STS 0x0063,R25 Store direct to data space
42
+00000057: 93800062 STS 0x0062,R24 Store direct to data space
43
10: break;
44
+00000059: C013 RJMP PC+0x0014 Relative jump
45
13: int y = 3;
46
+0000005A: E083 LDI R24,0x03 Load immediate
47
+0000005B: E090 LDI R25,0x00 Load immediate
48
+0000005C: 839A STD Y+2,R25 Store indirect with displacement
49
+0000005D: 8389 STD Y+1,R24 Store indirect with displacement
50
14: y = x;
51
+0000005E: 818B LDD R24,Y+3 Load indirect with displacement
52
+0000005F: 819C LDD R25,Y+4 Load indirect with displacement
53
+00000060: 839A STD Y+2,R25 Store indirect with displacement
54
+00000061: 8389 STD Y+1,R24 Store indirect with displacement
55
16: b = y;
56
+00000062: 8189 LDD R24,Y+1 Load indirect with displacement
57
+00000063: 819A LDD R25,Y+2 Load indirect with displacement
58
+00000064: 93900063 STS 0x0063,R25 Store direct to data space
59
+00000066: 93800062 STS 0x0062,R24 Store direct to data space
60
17: break;
61
+00000068: C004 RJMP PC+0x0005 Relative jump
62
19: b = 0;
63
+00000069: 92100063 STS 0x0063,R1 Store direct to data space
64
+0000006B: 92100062 STS 0x0062,R1 Store direct to data space
65
22: for (;;);
66
+0000006D: CFFF RJMP PC-0x0000 Relative jump
67
+0000006E: CFFF RJMP PC-0x0000 Relative jump
Beim kompilieren gibt's übrigens keine Warnungen...
Karl heinz Buchegger wrote:
> Das kann ich nicht erklären und ich denke auch nicht, dass> das so im C-Standard beabsichtigt ist.
Doch, das sieht sehr gewollt aus im C-Standard. Die freie Austausch-
barkeit von statements mit declarations ist nur in der Form eines
block-item (6.8.2 Compound statement) vorgesehen. Ein labeled
statement (6.8.1) besteht aber aus einem label, gefolgt von einem
statement -- nicht einem block-item, wie es nötig wäre, um dort
noch eine declaration unterzubringen.
Eine Variablendeklaration darf man natürlich direkt vor dem Label
unterbringen:
1
switch(OCR1A){
2
default:
3
break;
4
5
uint8_tblub;
6
case1:
7
blub=7;
8
usart_send_var("blub",blub);
9
break;
10
}
Ansonsten eben einen eigenen Block aufmachen nach dem case label.
Philipp Burch wrote:
> Interessant ist, was er aus meinem kleinen Testprogramm macht (Ohne> Optimierungen):
,,Interessant ist, wie langsam dieser Ferrari mit angezogener
Handbremse kriecht.''
Wer keine Optimierungen einschaltet, braucht sich auch nicht über
großen, umständlichen und langsamen Code beklagen.
> Philipp Burch wrote:>>> Interessant ist, was er aus meinem kleinen Testprogramm macht (Ohne>> Optimierungen):>> ,,Interessant ist, wie langsam dieser Ferrari mit angezogener> Handbremse kriecht.''>> Wer keine Optimierungen einschaltet, braucht sich auch nicht über> großen, umständlichen und langsamen Code beklagen.
Ich wollte auch nicht auf die Umständlichkeit hinaus, sondern auf die
Verwendung der Variablen, die eigentlich gar nicht existiert. Wenn ich
das richtig sehe, wird die Variable erstellt (Aber nur bei der
eigentlichen Deklaration und Definition initialisiert - ist ja auch
logisch) und ist im gesamten switch-Block verfügbar. Jedenfalls ist man
(Bzw. der Compiler) damit bestimmt auf der sicheren Seite.
Philipp Burch wrote:
> Ich wollte auch nicht auf die Umständlichkeit hinaus, sondern auf die> Verwendung der Variablen, die eigentlich gar nicht existiert. Wenn ich> das richtig sehe, wird die Variable erstellt (Aber nur bei der> eigentlichen Deklaration und Definition initialisiert - ist ja auch> logisch) und ist im gesamten switch-Block verfügbar. Jedenfalls ist man> (Bzw. der Compiler) damit bestimmt auf der sicheren Seite.
Dann solltest du das dazu schreiben. ;-)
Der GCC legt den Stackframe immer für die gesamte Funktion an,
zwischendurch wird da (außer bei alloca(), da bin ich mir nicht so
sicher) nicht manipuliert. Allerdings wird der Optimierer in aller
Regel das Zeug dann rauswerfen, und dann existieren die Werte der
jeweiligen Variablen wirklich oft nur für kurze Zeit in einem Register.
Weiter oben schriebst du:
> Mich verwundert, dass es so funktioniert, nicht, dass es ohne ';' nicht> funktioniert.
Ganz einfach: dein Semikolon hat syntaktisch eine leere Anweisung
erzeugt, damit war das "labeled statement" abgeschlossen, und du
befindest dich wieder auf der Ebene, bei der sich statements und
declarations beliebig abwechseln dürfen.
Jörg Wunsch wrote:
> Karl heinz Buchegger wrote:>>> Das kann ich nicht erklären und ich denke auch nicht, dass>> das so im C-Standard beabsichtigt ist.>> Doch, das sieht sehr gewollt aus im C-Standard. Die freie Austausch-> barkeit von statements mit declarations ist nur in der Form eines> block-item (6.8.2 Compound statement) vorgesehen. Ein labeled> statement (6.8.1) besteht aber aus einem label, gefolgt von einem> statement -- nicht einem block-item, wie es nötig wäre, um dort> noch eine declaration unterzubringen.>> Eine Variablendeklaration darf man natürlich direkt vor dem Label> unterbringen:
Hmm. Ich sehe worauf du hinaus willst.
Allerdings muss ich mich jetzt deiner weiter oben aufgeworfenen
Frage anschliessen:
Was ist der tiefere Sinn dahinter?
Irgendwie macht das schon den Eindruck, als ob die Erlaubnis
für Deklarationen mitten in den Statements nicht so ganz 100%
durchgezogen wurde.
Auf der anderen Seite: Dieses Statement-Deklaration mischen
gibt es ja in C++ schon länger als in C. In den entsprechenden
Normierungsgremien sind fähige Köpfe, sodass ich zuallererst
mal von der Annahme ausgehe, dass es da irgendeinen pathologischen
Fall gibt, der Probleme macht. Nur: ich finde ihn nicht. (Und
das macht mich wahnsinnig:-)
Da wäre noch der subtile Unterschied zwischen dem was man syntaktisch
darf, und dem was inhaltlich zulässig ist. Nicht alles was syntaktisch
geht ist auch erlaubt.
Dass man syntaktisch hinter Deklarationen springen darf, liegt an der
sonst erheblich komplexeren Syntax.
Dass man das dennoch nicht darf ist dann einen inhaltliche
Angelegenheit. Ein Compiler darf durchaus auch erst an der Stelle der
Variablendefinition den Stack-Frame aufbauen, wenn er das für sinnvoll
hält. Wird er üblicherweise nicht tun, jedenfalls nicht wenns nur um dem
Platz geht, weshalb es dann trotzdem funktioniert, aber es bleibt
offiziell illegal, hinter eine Definition zu springen.
Als Illustration mag ein Destruktor in C++ dienen. Der muss beim
Verlassen des Gültigkeitsbereichs aufgerufen werden. Was schon innerhalb
der Funktion geschehen kann, nicht erst am Ende. Und Analoges gilt dann
natürlich auch für den dazu passenden Konstruktor.
Der tiefere Sinn hinter der syntaktischen Eigenheit von Labels kann auch
schlicht in etwas liegen, was man bei anderer Gelegenheit als deutsches
Credo bezeichnen könnte: "das war schon immer so, da könnte ja jeder
kommen, wo kämen wir da hin" ;-). Schon die erste formale Syntax von
C/C++ war nicht der grosse Wurf und sie entwickelte sich aufgrund von
Anfangsfehlern mit der Zeit und den Standards von naja (K&R Original)
bis kotzwürg (C++).
> Schon die erste formale Syntax von>C/C++ war nicht der grosse Wurf und sie entwickelte sich aufgrund von>Anfangsfehlern mit der Zeit und den Standards von naja (K&R Original)>bis kotzwürg (C++).
Wow - kannst du da mal ein paar beispiele liefern. Ich stecke da nicht
so drin, und dachte bisher, dass C bzw. C++ gut und effizient seien.
Danke.
> Wow - kannst du da mal ein paar beispiele liefern. Ich stecke da nicht> so drin, und dachte bisher, dass C bzw. C++ gut und effizient seien.
Ich bezog mich ausdrücklich auf die formale Syntax, nicht auf Dinge wie
Nützlichkeit, Effektivität, Effizienz (anderes Thema).
Die allererste Version von C hatte keine typedefs. Bis dahin war die
Welt syntaktisch leidlich in Ordnung (ja, das else-Problem war schon
da). Dann kamen typedefs und aus normalen Namen wurden halbe
Schlüsselwörter. Der Compiler musste fortan typdef-Namen und die übrigen
Namen bereits im Parser auseinanderhalten. Weil man nur so eine
Deklaration erkennt. Wirth wusste es mitsamt der übrigen Algol-Gemeinde
besser und hat in Pascal CONST/VAR vorangestellt.
Dann kam Stroustrup und mutierte Datentypen zu Operatoren. Damit war
typename(x) nun Typkonvertierung oder Konstruktoraufruf. Vorher war es
die Deklaration von x mit überflüssiger Klammer drumrum. Syntaktisch
funktioniert beides, entscheidbar nur durch Konvention.
Weiter meinte Stroustrup, dass man die Initializer der Basisklassen per
Doppelpunkt einleitet. Also
classname(parameter) : (initializer) { ... }
Mal abgesehen davon, dass der classname hier syntaktisch genauso blöd
dasteht wie die oben erwähnten typedefs, erinnert das syntaktisch bis
zur geschweiften Klammer stark an ein Bitfeld.
> Wow - kannst du da mal ein paar beispiele liefern. Ich stecke da nicht> so drin, und dachte bisher, dass C bzw. C++ gut und effizient seien.
Zum anderen Thema: Der Erfolg von C und C++ hat wenig mit Qualität zu
tun. C war zum richtigen Zeitpunkt am richtigen Platz. In den 70ern gabe
es an Compilersprachen hauptsächlich FORTRAN 66, COBOL, Algol, Pascal, C
und PL/1.
Davon ist PL/1 erstens eine nackte Katastrophe, zweitens so stark mit
IBM verklebt dass es niemand sonst in die Finger nehmen wollte.
COBOL ist für wirklich alles ungeeignet, besonders ungeeignet aber für
alles ausserhalb der kaufmännischen Sphäre. Und FORTRAN 66 fehlt vieles
was Informatiker so lieben, beispielsweise die Rekursion.
Pascal ist sprachlich sauber konstruiert, aber wie alle Sprachen von
Wirth für genau einen einzigen Einsatzzweck. In diesem Fall den
Anfangssemestern das Programmieren beizubringen. Mehr geht damit nicht.
Jedenfalls nicht mit dem Sprachumfang den Wirth definierte.
Blieb praktisch nur C übrig (von Algol weiss ich zu wenig). Das war
einfach da und konnte irgendwie fast alles was man benötigte. Und nicht
nur als Sprache, sondern gleich auch noch mitsamt Betriebssystem,
Compiler und vergleichsweise günstiger Maschine (PDP11) dazu. Inklusive
kostenlosem Quellcode, sowohl vom Compiler als auch vom Betriebssystem.
Wie geschaffen um an Unis damit herumzuspielen.
Was also konnte und wollte man an Unis verwenden?
Was also haben dann all jene verwendet, die von Assembler weg wollten
und eine Sprache brauchten, mit denen man nicht nur Gehälter oder
Umlaufbahnen berechnen kann, sondern Betriebssysteme, Compiler,
interaktive Programme, ... programmieren?
Und so ist C zwar ungemein populär, aber wer mit mit Programmiersprachen
nicht nur programmiert, sondern sich auch ein bischen mit deren
Systematik und Potential (positiv wie negativ) auseinandersetzt, der ist
vom Siegeszug von C alles andere als begeistert.
Und da C++ die negativen Eigenschaften von C der Kompatibilität zuliebe
sämtlich geerbt hat, kann man dem, was sprachlich hätte geordnet werden
können, eben nur durch Disziplin und Konvention beikommen. So man davon
hat.
Um das mal auf das Thema dieses Boards zu bringen: Für Controller der
AVR-Klasse braucht man eine Sprache, die ziemlich genau den
Anforderungen der 70er entspricht. Dafür ist C bei aller Kritik
einigermassen brauchbar, die Hardware-Nähe hat man da ja sowieso. Ada
wäre mir zwar erheblich lieber, aber mehr als ein paar kleine
Spielplätze für Aussenseiter wird da nicht mehr herauskommen.
PS: Das ist wie mit x86-Prozessoren. Absolut jeder, der sich mit dem
Thema Rechnerarchitektur ein bischen auskennt, schimpft darüber. Aber
sie sind halt da und man muss damit leben.