Hallo, ich gebe zu, die Frage im Betreff ist schon etwas komisch, aber
ich wundere mich über die Reaktion meines Programmes, vielleicht kann da
mal jemand etwas zu sagen...
1
#include<stdio.h>
2
3
intmain()
4
{
5
chardatenfeld[5];
6
7
printf("String eingeben: ");
8
scanf("%s",&datenfeld);
9
10
printf("%s\n",datenfeld);
11
12
system("pause");
13
return0;
14
}
Wenn ich etwas sehr langes (>5 Zeichen) eingebe kommt kein Fehler, es
werden nur ca. 2 Zeichen abgeschnitten, der restliche String (auch >5
Zeichen) wird aber ausgegeben. Warum kommt kein fehler, wenn man mehr
als 5 eingibt, und warum werden die auch ausgegeben, wenn die Variable
nur 5 fassen kann?
Hansi schrieb:> Wenn ich etwas sehr langes (>5 Zeichen) eingebe kommt kein Fehler, es> werden nur ca. 2 Zeichen abgeschnitten, der restliche String (auch >5> Zeichen) wird aber ausgegeben. Warum kommt kein fehler, wenn man mehr> als 5 eingibt, und warum werden die auch ausgegeben, wenn die Variable> nur 5 fassen kann?
Weil es in C für solcher Art von Fehlern prinzipiell keine vorgesehene
Fehlerprüfung gibt. Das ist das Bier des Programmierers das zu
verhindern.
Warum dein Programm "scheinbar" funktioniert?
Weil du durch die lange Eingabe zufällig keine unmittelbar
lebenswichtigen Speicherbereiche niedergebügelt hast. Dumm gelaufen,
denn das ist der schlimmere Fall. Wenn dein Programm gleich einfach
abgestürzt wäre, wäre dir aufgefallen, das da was nicht stimmt.
> wenn die Variable nur 5 fassen kann?
Arrayzugriffe werden in C zur Laufzeit nicht auf Gültigkeit überwacht.
Alles was du mehr eingegeben hast, als das Array fassen kann, wird
einfach beginnend mit dem Array sequentiell im Speicher
weitergeschrieben. Wenn da keine andere Variablen liegen, kannst du
daher damit davonkommen. Fehler ist es aber trotzdem.
Probier mal folgendes
1
intmain()
2
{
3
inti=5;
4
chardatenfeld[5];
5
intj=5;
6
7
...
also eine Variable vor bzw. hinter dem Array, und lass dir deren Werte
vor bzw. nach deiner Eingabe ausgeben. Die Chancen stehen gut, dass eine
der beiden ihren Wert magisch verändert hat.
Du kannst auch mal etwas wirklich Langes einzugeben (so 30 Zeichen oder
mehr). Dann wird mit hoher Wahrscheinlichkeit die Rücksprungadresse auf
dem Stack überschrieben werden, so dass das Programm beim Beenden (nach
Drücken der Enter-Taste) einen Segfault, illegal Instruction, allgemeine
Schutzverletzung o.ä. auslösen wird.
Bei Eingaben, die nur wenig länger als das erlaubte Maximum von 4 Zei-
chen sind, ist die Chance groß, dass keine lebenswichtigen Speicher-
bereiche überschrieben werden.
Wenn du glaubst, daß dieses Programm wirklich funktioniert, dann lege
nach dem String "datenfeld[5]" noch eine zweite Variable an, die du
ebenfalls ausgibst (und ggf. vorher einliest).
Dann wirst sehen, wo die "überzähligen" Bytes landen, wenn du mehr als 4
Zeichen eingibst.
>> gibt auch keinen Fehler aus, ist das dann nicht schon etwas> verwunderlich?
So bringt das nix, so weiß der Compiler ja wie lange der String ist, und
ignoriert deine [5] und reserviert genug Speicher.
Deshalb gibt man gar keine Länge an, wenn man mit einem String
initalisiert
Gemeint war, wenn die eingabe per scanf wirklich lange ist, aber du den
char array nur mit 5 elementen initalisiert hast. dann müsste dir dein
programm um die Ohren fliegen.
Ist bei C so, "no seatbelts". Du bist selber dafür verantwortlich genug
speicher zu reservieren und dich später beim befüllen daran zu halten.
gruß cyblord
eine sehr lange Eingabe über die komplette Konsolenbreite und noch mehr
wird genau so ausgegeben. Jetzt kommt aber der Fehler "Das Programm
funktioniert nicht mehr richtig" beim beenden mit system("pause");.
Was verursacht dort den Fehler? Ein Vorredner sagte, dass bereits das
i=4; und j=4; vor bzw. nach der deklaration des Datenfeldes einen Fehler
hervorruft, weil der Speicher vor und nach dem Datenfeld fast zeitgleich
zugewiesen wird...
Hansi schrieb:> weil der Speicher vor und nach dem Datenfeld fast zeitgleich
?
Auch dein Programm, d.h. die Befehle an die CPU, kannst du damit
überschreiben, dann geht garnichts mehr.
Der Speicher von i wird vermutlich auf eine Zelle direkt vor dem String,
j direkt nach dem String zugewiesen. Das erzeugt keinen Fehler. Wenn du
nun einen zu langen String eingibst, überschreibt dieser die
nachfolgenden Speicherzellen, wo auch j gespeichert wird. Der Wert von j
hat sich damit wie von Geisterhand verändert, weil ja die Speicherzelle
mit einem Zeichen deines Strings überschrieben wird.
cyblord ---- schrieb:> So bringt das nix, so weiß der Compiler ja wie lange der String ist, und> ignoriert deine [5] und reserviert genug Speicher.> Deshalb gibt man gar keine Länge an, wenn man mit einem String> initalisiert
Nee, das Feld hat 5 Elemente (5 mal 'a', keine Terminierung) und der
Rest der Initialisierung wird ignoriert.
Hansi schrieb:> Was verursacht dort den Fehler?
Die Frage sollte lauten: welches Buch ist gut, um C zu lernen?
Wenn du dir C häppchenweise in Foren vorkauen lässt, verschwendest du
nur deine und unsere Zeit.
Hansi schrieb:> Was verursacht dort den Fehler?
Das Fehlen der Nullterminierung.
Hansi schrieb:> Warum kommt kein fehler, wenn man mehr> als 5 eingibt, und warum werden die auch ausgegeben, wenn die Variable> nur 5 fassen kann?
Warum nicht? Das Programm ist nicht verpflichtet, abzustürzen, wenn ein
Fehler drin ist. Man nennt das in C-Sprech "undefiniertes Verhalten",
und es bedeutet eben genau das, also daß nicht definiert ist, was
passiert. Das Programm kann arbeiten wie erwartet oder auch anders oder
gar nicht oder rückwärts.
>> Was verursacht dort den Fehler? Ein Vorredner sagte, dass bereits das> i=4; und j=4; vor bzw. nach der deklaration des Datenfeldes einen Fehler> hervorruft, weil der Speicher vor und nach dem Datenfeld fast zeitgleich> zugewiesen wird...
Ich hab dir aber auch gesagt: Lass dir die Werte von i und j mal vor und
mal hinter der Eingabe ausgeben.
Also
1
printf("Vor der Eingabe: i = %d, j = %d\n",i,j);
2
3
scanf("%s",&datenfeld);
4
printf("%s\n",datenfeld);
5
6
printf("Nach der Eingabe: i = %d, j = %d\n",i,j);
Und jetzt füttere da mal einen ordentlich String in die Eingabe rein.
Vergleich die Werte von i und j VOR der Eingabe mit den Werten DANACH.
Die Chancen stehen gut, dass eine von beiden ihren Wert verändert hat.
Und das willst du ja schliesslich nicht, dass du an 'datenfeld' etwas
zuweist und dadurch verändert sich zb. die Startfreigabe für eine
Atomrakete, die in einer anderen Variablen aufbewahrt wird.
Selbst wenn das Programm nicht sofort abstürzt, würde ich das nicht als
'funktionieren' bezeichnen.
Hansi schrieb:> eine sehr lange Eingabe über die komplette Konsolenbreite und noch mehr> wird genau so ausgegeben. Jetzt kommt aber der Fehler "Das Programm> funktioniert nicht mehr richtig" beim beenden mit system("pause");.>> Was verursacht dort den Fehler?
Das, was ich oben schrieb:
Yalu X. schrieb:> Dann wird mit hoher Wahrscheinlichkeit die Rücksprungadresse auf dem> Stack überschrieben werden, so dass das Programm beim Beenden (nach> Drücken der Enter-Taste) einen Segfault, illegal Instruction,> allgemeine Schutzverletzung o.ä. auslösen wird.
Die Funktion main() ist nichts anderes als ein Unterprogramm, das vom
C-Laufzeitsystem (dem eigentlichen Hauptprogramm) aufgerufen wird. Damit
der Programmablauf nach Beendigung eines Unterprogramm ins Hauptprogramm
zurückfindet, muss die Rücksprungadresse vor dem Unterprogrammaufruf auf
dem Programmstack gespeichert werden. In deinem Beispiel überschreibt
der Arrayüberlauf diese Rücksprungadresse, so dass nach Beendigung von
main() der Rücksprung ins Laufzeitsystem nicht mehr funktioniert und
deswegen seltsame Dinge passieren.
Sacht mal.
Datenfeld[] ist doch schon ein Pointer.
&Datenfeld zeigt wer weis wo hin.
Oder ?
scanf erwartet einen pointer, keinen pointer auf pointer.
Oder brauch ich ne neue Brille ?
NurEinGast schrieb:> Sacht mal.> Datenfeld[] ist doch schon ein Pointer.
Nein.
> scanf erwartet einen pointer, keinen pointer auf pointer.> Oder brauch ich ne neue Brille ?
Ist ein beliebter Fehler (und ich hab auch nicht drauf geachtet).
Im Prinzip hast du recht, dass der & nicht richtig ist. Aber auch
Compiler wissen, dass ein & auf ein Array als ganzes eigentlich ein
Fehler ist und was eigentlich gemeint ist. Viele Compiler ignorieren
hier stillschweigend den &.
Mike Mike schrieb:> Datenfeld ist der Pointer aufs erste Element
Genau
> Ist das gleiche wie &Datenfeld[0]
sehe ich auch so.
> Und den erwartet Scanf
So ist es.
Dann sollte man aber auch scanf("%s", datenfeld); schreiben
und nicht scanf("%s", &datenfeld);
Das viele Compiler das ignorieren beunruhigt mich eher als das ich es
schön finde. Mir ist es schon lieber, wenn der Compiler das tut was ich
hinschreibe und nicht "was er denkt was ich wohl gemeint haben werde".
Ich denke - in diesem Fall lieber das "&" weglassen, auch wenn mancher
Compiler es klammheimlich repariert.
Frank W. schrieb:> Dann sollte man aber auch scanf("%s", datenfeld); schreiben> und nicht scanf("%s", &datenfeld);>> Das viele Compiler das ignorieren beunruhigt mich eher als das ich es> schön finde. Mir ist es schon lieber, wenn der Compiler das tut was ich> hinschreibe und nicht "was er denkt was ich wohl gemeint haben werde".
halt stop.
datenfeld ist in dem Fall identisch mit &datenfeld[0]
&datenfeld ist Zeiger auf das ganze Feld
char x[5];
char (*px)[5] = &x;
das Problem ist, dass beim scanf Aufruf die Konversion vorgenomen wird
char * richtig = px;
in dem Fall ok, nicht aber wenn
char * richtig = px+1;
da liegt man 5 Zeichen dahinter
So - jetzt ganz langsam für ältere Herrschaften
Daniel schrieb:> datenfeld ist in dem Fall identisch mit &datenfeld[0]> &datenfeld ist Zeiger auf das ganze Feld
Soweit so gut.
1
char x[5]; Ein Array mit 5 char elementen.
2
char (*px)[5] = &x; Ein Pointer auf ein Char Array mit 5 elementen
3
4
char * richtig = px; Ein Char pointer, der mit px initialisiert wird.
5
( Wird der Compiler aber mit warning anmeckern )
>> char * richtig = px;> in dem Fall ok, nicht aber wenn> char * richtig = px+1;> da liegt man 5 Zeichen dahinter
Ja sicher. Das hat man dem Compiler ja auch gesagt mit "px+1".
"richtig" zeigt nun auf das nächste "Char Array mit 5 elementen", ( dass
es nebenbei bemerkt hier nicht gibt. Ptr zeigt also in's nirvana )
Nun aber zurück zum Anfang des Threads.
Bei : scanf("%s", &datenfeld); bekommt scanf einen "Pointer auf ein Char
Array mit 5 Elementen" als parameter.
Nun will aber - zumindest mein C-Compiler/C-Lib - dass scanf("%s"...)
einen char * übergeben bekommt und keinen "Pointer auf ein Char Array
mit 5 Elementen".
Der Compiler macht das schon richtig - aber er gibt eine Warning aus.
Und wie ich finde gibt er die Warning zu Recht aus.
Bei :
scanf("%s", datenfeld);
und bei
scanf("%s", &datenfeld[0]);
bekommt scanf dass was es erwartet.
Einen "Pointer auf Char" - und gibt also keine Warning aus.
Vielleicht kleinlich - aber ich habe mir angewöhnt auch Warnings Ernst
zu nehmen und sie zu vermeiden.
Eigentlich - Nein, nicht kleinlich.
Warnings sollten vermieden werden wenn immer es geht.
Frank W. schrieb:> So - jetzt ganz langsam für ältere Herrschaften
Ich denke, man sollte hier noch dazuschreiben:
Die Diskussion geht hier nur noch um den Datentyp, den das Ergebnis hat.
Der Zahlenwert ist identisch.
> Nun will aber - zumindest mein C-Compiler/C-Lib - dass scanf("%s"...)> einen char * übergeben bekommt und keinen "Pointer auf ein Char Array> mit 5 Elementen".
Das Problem an dieser Stelle ist, dass printf eine variadische Funktion
ist. D.h. das scanf will an dieser Stelle weder das eine noch das
andere. Sondern es nimmt was es kriegt und holt sich aus dem
Formatstring raus, was es mit dem Zahlenwert anstellen soll.
numerisch sind beide Pointerwerte identisch. Das das ursrpünglich mal
ein Pointer auf ein Feld war, ist innerhalb von scanf nicht mehr von
Belang. Durch das %s wird dem numerischen Zahlenwert der Datentyp
Pointer auf char zugewiesen. Und da sich die beiden numerisch nicht
unterscheiden, kommt das richtige raus.
> Der Compiler macht das schon richtig - aber er gibt eine Warning aus.> Und wie ich finde gibt er die Warning zu Recht aus.
Ergänzend:
Das macht der gcc. Weil er eine Erweiterung hat, die bei printf/scanf
den Formatstring untersucht. Verpflichtet ist er dazu nicht. Und andere
Compiler machen das nicht.
> Vielleicht kleinlich
Nö. Überhaupt nicht. Wer nicht 'kleinlich' ist, läuft in so manche
Falle, deren Natur sich erst zeigt, wenn man kleinlich ist.
> - aber ich habe mir angewöhnt auch Warnings Ernst> zu nehmen und sie zu vermeiden.> Eigentlich - Nein, nicht kleinlich.> Warnings sollten vermieden werden wenn immer es geht.
Das ist die richtige Einstellung.
In nicht wenigen Firmen gilt die Devise:
* Warning-Level hoch
* Code MUSS ohne Warnungen compiliert werden können.