Forum: PC-Programmierung Wieso funktioniert das Programm?


von Hansi (Gast)


Lesenswert?

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
int main()
4
{
5
  char datenfeld[5];
6
7
  printf("String eingeben: ");
8
  scanf("%s", &datenfeld);
9
  
10
        printf("%s\n", datenfeld);
11
  
12
        system("pause");
13
  return 0;
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?

von Karl H. (kbuchegg)


Lesenswert?

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
int main()
2
{
3
  int i = 5;
4
  char datenfeld[5];
5
  int j = 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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von frame (Gast)


Lesenswert?

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.

von Hansi (Gast)


Lesenswert?

Yalu X. schrieb:
> u kannst auch mal etwas wirklich Langes einzugebe
1
int main()
2
{
3
int i=4;
4
char datenfeld[5]="aaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccccddddddddddddddddd";
5
int j=4;
6
...

gibt auch keinen Fehler aus, ist das dann nicht schon etwas 
verwunderlich?

von Cyblord -. (cyblord)


Lesenswert?

Hansi schrieb:
> Yalu X. schrieb:
>> u kannst auch mal etwas wirklich Langes einzugebe
>
>
1
> int main()
2
> {
3
> int i=4;
4
> char
5
> datenfeld[5]="aaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbccccccccccccccccccccddddddddddddddddd";
6
> int j=4;
7
> ...
8
>
>
> 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

von Hansi (Gast)


Lesenswert?

Jetzt sieht das Programm so aus:
1
#include <stdio.h>
2
3
int main()
4
{
5
  int i=4;
6
  char datenfeld[5]="12345";
7
  int j=4;
8
9
  scanf("%s", &datenfeld);
10
  printf("%s\n", datenfeld);
11
12
  system("pause");
13
  return 0;
14
}

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...

von Witte (Gast)


Lesenswert?

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.

von Ich (Gast)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

Hansi schrieb:
> Jetzt sieht das Programm so aus:
>
1
> #include <stdio.h>
2
> 
3
> int main()
4
> {
5
>   int i=4;
6
>   char datenfeld[5]="12345";
7
>   int j=4;
8
> 
9
>   scanf("%s", &datenfeld);
10
>   printf("%s\n", datenfeld);
11
> 
12
>   system("pause");
13
>   return 0;
14
> }
15
>
>

> 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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von NurEinGast (Gast)


Lesenswert?

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 ?

von Mike M. (mikeii)


Lesenswert?

Datenfeld ist der Pointer aufs erste Element
Ist das gleiche wie &Datenfeld[0]
Und den erwartet Scanf

von Karl H. (kbuchegg)


Lesenswert?

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 &.

von Frank W. (Firma: DB1FW) (frankw) Benutzerseite


Lesenswert?

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.

von Daniel (Gast)


Lesenswert?

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

von Frank W. (Firma: DB1FW) (frankw) Benutzerseite


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.