Speicherzugriffsverletzung durch strcpy()
-
ahhh ja. der kleine aber feine Unterschied
C <> C++Dahingehend, muß ich mich als fpc-Programmierer erst noch gewöhnen

nun von einer anderen noch auch den tipp mit der Speicherverwaltung erhalten und in gEdit die Sprache mal auf c ungestellt. und was sehen mein augen? es geht
aber wie schon gesagt das sind hier in C die kleinen feinen aber auch wohl wichtigen Unterschiedeund danke für den Buchtipp... werd i mal schauen, ob das als eBook bekomme.
-
Der Code ist auch in C nicht legal. Dass es bei dir funktioniert, ist erstaunlich und reiner Zufall.
Auch sind die Unterschiede zwischen C und C++ alles andere als "klein und fein". In C++ sähe dein Vorhaben etwa so aus:
#include <iostream> #include <string> std::string TestFkt(std::string const &sValue) { std::string result; std::cout << "läuft" << std::endl; result = sValue; std::cout << "läuft" << std::endl; return result; } int main() { std::string text; text = TestFkt("Hallo, Otto!"); std::cout << "Hallo, Welt : --> " << text << std::endl; }In C könnte man die grobe Struktur deines Programms nur mit dynamischer Speicherverwaltung annähernd erhalten:
#include <stdio.h> #include <stdlib.h> #include <string.h> char *Testfkt(const char *sValue) { char *zeichen; zeichen = malloc(strlen(sValue + 1)); printf("1 läuft\n"); /* Wenn du mit printf debuggen willst, musst du jedes mal den Stream flushen, * sonst kann es dir passieren, dass nicht alles, was bisher nach stdout * geschrieben wurde, auch schon in der Konsole angezeigt wird. Natürlich ist * es sinnvoller, einen richtigen Debugger zu benutzen. */ fflush(stdout); strcpy(zeichen, sValue); printf("1 läuft\n"); fflush(stdout); return(zeichen); } int main(void) { /* Die 100 ist willkürlich gewählt; wichtig ist, dass genug Speicher * vorhanden ist, um den String aufzunehmen. Außerdem: schau dir mal strncpy * an. */ char text[100], *ptr; ptr = Testfkt("Hallo Otto"); strcpy(text, ptr); printf("Hallo Welt : ---> %s\n", text); free(ptr); return 0; }Grundsätzlich brauchst du, um in Speicher herumzuschreiben, Speicher, der dir gehört. In uninitialisierte Zeiger zu schreiben, ist in allen Sprachen, die Zeiger kennen, ein Fehler.
-
seldon schrieb:
Der Code ist auch in C nicht legal. Dass es bei dir funktioniert, ist erstaunlich und reiner Zufall.
Auch sind die Unterschiede zwischen C und C++ alles andere als "klein und fein". In C++ sähe dein Vorhaben etwa so aus:
#include <iostream> #include <string> std::string TestFkt(std::string const &sValue) { std::string result; std::cout << "läuft" << std::endl; result = sValue; std::cout << "läuft" << std::endl; return result; } int main() { std::string text; text = TestFkt("Hallo, Otto!"); std::cout << "Hallo, Welt : --> " << text << std::endl; }In C könnte man die grobe Struktur deines Programms nur mit dynamischer Speicherverwaltung annähernd erhalten:
#include <stdio.h> #include <stdlib.h> #include <string.h> char *Testfkt(const char *sValue) { char *zeichen; zeichen = malloc(strlen(sValue + 1)); printf("1 läuft\n"); /* Wenn du mit printf debuggen willst, musst du jedes mal den Stream flushen, * sonst kann es dir passieren, dass nicht alles, was bisher nach stdout * geschrieben wurde, auch schon in der Konsole angezeigt wird. Natürlich ist * es sinnvoller, einen richtigen Debugger zu benutzen. */ fflush(stdout); strcpy(zeichen, sValue); printf("1 läuft\n"); fflush(stdout); return(zeichen); } int main(void) { /* Die 100 ist willkürlich gewählt; wichtig ist, dass genug Speicher * vorhanden ist, um den String aufzunehmen. Außerdem: schau dir mal strncpy * an. */ char text[100], *ptr; ptr = Testfkt("Hallo Otto"); strcpy(text, ptr); printf("Hallo Welt : ---> %s\n", text); free(ptr); return 0; }Grundsätzlich brauchst du, um in Speicher herumzuschreiben, Speicher, der dir gehört. In uninitialisierte Zeiger zu schreiben, ist in allen Sprachen, die Zeiger kennen, ein Fehler.
Danke dir auch und das das nun bei mir läuft ist kein Zufall..., nur eine kleine Einstellung in gEdit und verwendung des gcc combilers.
das was du in C meinst schaut nun bei mir funktionsfähig so aus...
#include <stdlib.h> #include <stdio.h> #include <string.h> char *Testfkt(const char *sValue) { char *zeichen = malloc(100 * sizeof(char)); strcpy(zeichen, sValue); return(zeichen); } int main() { char *text = Testfkt("Hallo Otto"); printf("Hallo Welt : ---> %s\n", text); }und was die Zeiger in anderen sprachen angeht... nun damit kenn ich mich zugegeben nicht aus, da ich da Strings direkt verwenden kann. obwohl ich weiß, das die strings z.B. in fpc nur "MÜLL" von der verwaltung sind.
meine ziehsetzung ist es am ende nur teilprogramme (*.so und co) in C/C++ zu schreiben.
und was den Debugger angeht, das ich sowieso nur ein billiges Ausgabefeld, was ich vor und nach der Eigentlichen Funktion einsetzt, um zu sehen, wo der Fehler genau auftritt.
Mehr soll er auch nicht machen.
-
Doch, das ist Zufall. Lass mal valgrind drüber, der wird sich schön darüber beschweren. Schon eine Änderung der Optimierungsparameter oder ein Compilerupdate kann dir solchen Kram auseinanderhauen. So was kann auch zwei Monate laufen und nach einem Bibliotheks- oder Kernelupdate abstürzen, oder die Erweiterung des Programms kann daran scheitern, dass sich solche Fehler auf einmal bemerkbar machen. Ob es funktioniert oder nicht kann sogar davon abhängen, welche Programme vorher gestartet und beendet wurden, und wenn du ganz viel Pech hast, schreibst du über eine Rücksprungadresse und rufst wer-weiß-was auf.
In Speicher zu schreiben, der dir nicht gehört, erzeugt undefiniertes Verhalten. So etwas sollte man tunlichst vermeiden.
Wo hast du diese Praxis überhaupt her? Jedenfalls solltest du dir schleunigst eine andere Lernquelle suchen.
Übrigens musst du Speicher, den du mit malloc anforderst, mit free wieder freigeben, sonst hast du ein Speicherleck.
-
seldon schrieb:
Doch, das ist Zufall. Lass mal valgrind drüber, der wird sich schön darüber beschweren. Schon eine Änderung der Optimierungsparameter oder ein Compilerupdate kann dir solchen Kram auseinanderhauen. So was kann auch zwei Monate laufen und nach einem Bibliotheks- oder Kernelupdate abstürzen, oder die Erweiterung des Programms kann daran scheitern, dass sich solche Fehler auf einmal bemerkbar machen. Ob es funktioniert oder nicht kann sogar davon abhängen, welche Programme vorher gestartet und beendet wurden, und wenn du ganz viel Pech hast, schreibst du über eine Rücksprungadresse und rufst wer-weiß-was auf.
In Speicher zu schreiben, der dir nicht gehört, erzeugt undefiniertes Verhalten. So etwas sollte man tunlichst vermeiden.
Wo hast du diese Praxis überhaupt her? Jedenfalls solltest du dir schleunigst eine andere Lernquelle suchen.
Übrigens musst du Speicher, den du mit malloc anforderst, mit free wieder freigeben, sonst hast du ein Speicherleck.
valgrind Meldet leider
valgrind: Teilporg: command not found
Das mit dem Speicherleak hab ich schon auch schon gelöst.
Ähmmm und was dein Frage nach der Praxis angeht. nun, dazu muß ich leider sagen, das das Buch mit der ISBN: 3 8373 1405 4 mein Lehrer ist :S
passt mir ja selber auch nicht wirklich, aber 'ne andere Alternative hab ich nun mal nicht.
-
Bei der ISBN musst du dich vertippt haben; die Prüfziffer stimmt nicht. Meintest du 3827314054? Wenn ja, dann kenne ich das Buch nicht. Steht da wirklich drin, dass man in uninitialisierte Zeiger schreiben darf?
Was valgrind angeht, "valgrind ./Teilporg" sollte funktionieren - unter der Annahme, dass deine ausführbare Datei Teilporg heißt und im aktuellen Verzeichnis liegt.
Ich versuche mal, die Problematik zu erklären: Die übliche Behandlung von Strings in C läuft so, dass man irgendwo im Speicher eine Reihe von Zeichen, die durch ein Null-Byte abgeschlossen wird, liegen hat und einen Zeiger auf das erste Zeichen dieser Folge herumreicht. Der String muss nicht unbedingt in schreibbarem Speicher liegen; String-Literale tun das üblicherweise beispielsweise nicht. Folgendes ist ein Fehler:
char *p = "foo bar"; /* Selbst das ist ohne const schon ziemlich schlechter Stil */ p[0] = 'F'; /* KAWUMM! */Um veränderbare Strings zu erhalten, brauchst du Speicher, der dir gehört. Das geht am Einfachsten über Arrays:
char s[] = "foo bar"; s[0] = 'F'; /* Kein Problem. */wobei du dann aufpassen musst, dass du nicht über das Ende des Arrays hinausschreibst:
char s[] = "foo bar"; strcat(s, " baz"); /* KAWUMM! */...wie es halt bei allen Arrays der Fall ist. Du musst genug Speicher anlegen, dass die längste Form, die der String annimmt, hineinpasst:
char s[20] = "foo bar"; strcat(s, " baz"); /* kein Problem */ strcat(s, "01234567890123456789"); /* KAWUMM! Nicht genug Platz! */Allerdings gibt es zwei Probleme mit Arrays: Ihre Größe muss unter Umständen zur Compilezeit feststehen (C99 hebt diese Beschränkung zwar auf, aber Microsoft hat das bis heute nicht implementiert), und ihre Gültigkeit endet mit ihrem Block. Beispiel:
char *p; { char buf[100]; p = buf; } /* bufs Gültigkeit endet hier! */ p[0] = '\0'; /* KAWUMM! */Das ist vor allem im Zusammenhang mit Funktionen für Anfänger ein beliebter Stolperstein. Beispiel:
char *TestFunktion(char const *sValue) { char buf[100] = {}; strncpy(buf, sValue, 99); return buf; /* KAWUMM! */ } /* bufs Gültigkeit endet hier! */Wo diese Beschränkungen problematisch sind, braucht es malloc (oder calloc) und free. Beispiel:
char *TestFunktion(char const *sValue) { char *result = malloc(strlen(sValue) + 1); /* String-Länge plus ein Byte für den Sentinel */ if(result != NULL) { strcpy(result, sValue); } return result; } /* ... */ char *ptr = TestFunktion("foo bar"); /* ... */ free(ptr); /* Freigabe nicht vergessen! */Diese Regeln zu missachten, kann zu auf den ersten Blick sehr merkwürdigen Fehlerbildern führen. Im Fall uninitialisierter Variablen beispielsweise ist es häufig so, dass zunächst einfach das drinsteht, was vorher an der entsprechenden Stelle halt im Speicher stand. Wenn da vorher ein Buffer lag, auf den noch ein Zeiger zeigt, wie im obrigen Beispiel, kann das Anlegen und Rechnen mit neuen Zahlenvariablen dazu führen, dass sich der Stringinhalt des Zeigers anscheinend ändert, und das Schreiben in den String kann dazu führen, dass die Zahlen merkwürdige Werte annehmen. Wenn du über ein Bufferende hinausschreibst, kann es dir passieren, dass du in die Verwaltungsinformationen auf dem Stack hineinschreibst und der Code plötzlich wild in der Gegend herumspringt. Es kann dir auch passieren, dass dein Programm dadurch für Hacker angreifbar wird, die diesen Effekt geschickt ausnutzen. Undefiniertes Verhalten ist eine ernste Sache, über die man nicht einfach hinwegsehen kann.
Zur Verdeutlichung, wie so etwas passieren kann, habe ich mal ein kleines Beispiel zusammengeschrieben, von dem ich vermute, dass es, wenn der Compiler keine aktiven Schritte dagegen unternimmt, ein vorhersehbares Verhalten erzeugen sollte. Im Debug-Modus von MSVC wird das beispielsweise nicht funktionieren, weil der uninitialisierte Variablen mit Blödsinn initialisiert, um solche Fehler leichter auffindbar zu machen, und gccs mudflap wird der Code auch nicht überleben. Unter diesen Einschränkungen:
#include <stdio.h> #include <string.h> void foo(char *buf) { char *p = buf; puts(buf); } void bar(char *buf) { /* Dieser Zeiger liegt nach dem Aufruf von bar aus main da, wo p in foo * nach dem Aufruf aus main lag. Er hat deswegen zufällig den selben Wert, * zeigt also auf buf in main. */ char *p; strcpy(p, "foo"); /* Undefiniertes Verhalten! */ } int main(void) { char buf[100] = ""; foo(buf); bar(0); /* buf beinhaltet jetzt (wahrscheinlich) "foo" */ puts(buf); return 0; }Ich hoffe, du kannst mir glauben, dass solche Probleme zu richtig unangenehmem Debugging führen.
-
char *Testfkt(const char *str) { char *p = NULL; if (str && (p = malloc(sizeof(char) * (strlen(str) + 1)))) strcpy(p, str); return p; }
-
Ich hoffe, du kannst mir glauben, dass solche Probleme zu richtig unangenehmem Debugging führen.[/quote]
Das ist mal eine erklärung, welche auch wirklich jeder versteht. klar und nachvollziehbar. und was die ISBN angeht die szeht auch so auf meinem Buch drauf. ist aber auch die, wie du meinst.
Habe aber mal meine Teilprog alles wie folgt umgeschrieben
#include <stdlib.h> #include <stdio.h> #include <string.h> char *Testfkt(const char *sValue) { char *zeichen = malloc(sizeof(char)+1); strcpy(zeichen, sValue); return(zeichen); } int main() { char zeichen; char *text = Testfkt("HALLO OTTTTOOOOOOOOO"); free(text); printf("Hallo Welt : ---> %s\n", text); }und via dem tool durchlaufen lassen. ergebnis??? All heap blocks were freed -- no leaks are possible.
ich glaube nun hab ich es gerafft. nun würde ich mir allerdings noch eine makefile anlegen, welche alles mit einmal macht

ergogcc -c Teilprog.c gcc -o Teilprog Teilprog.o ./Teilprog // <--- kann ggf entfallen würd ich sagen. valgrind --leak-check=full ./Teilprog
-
Der von dir gezeige Code ist leider noch immer ziemlich fehlerhaft.
Du reservierst nur 2 Zeichen, das ist nicht genug:
char *zeichen = malloc(sizeof(char)+1);Richtig wäre:
char *zeichen = malloc(sizeof(char) * (strlen(sValue) + 1));Außerdem gibst du den Speicher frei und greifst danach darauf zu. Auch das ist ein katastrophaler Fehler. Du kannst/darfst den Speicher erst freigeben, wenn er nicht mehr benötigt wird.
Richtig wäre daher:
char *text = Testfkt("HALLO OTTTTOOOOOOOOO"); printf("Hallo Welt : ---> %s\n", text); free(text);
-
Es ist übrigens unnötig, jedes Mal den kompletten Post zu zitieren. Das macht es nur schwieriger, den Thread zu lesen

-
@kjhhjk komischerweise hab ich dennoch keine Probleme erhalten, aber danke nochmal für den Hinweis.
-
maik81ftl schrieb:
@kjhhjk komischerweise hab ich dennoch keine Probleme erhalten, aber danke nochmal für den Hinweis.
In dem Fall ist dein Compiler einfach ziemlich schlecht. Es ist immer fatal, wenn ein Programm trotz solch krasser Fehler weiterläuft. Vielleicht hast du aber auch einfach dem Compiler gesagt, das er das nicht so eng sehen soll? Wer weiß.
-
Das kann dir mit jedem Compiler passieren. Der Kernel weist dir ja den Speicher üblicherweise nicht byteweise zu, sondern in Speicherseiten - wenn du jetzt nicht gerade über das Ende einer Speicherseite hinausschreibst, ist es möglich, das so etwas einfach durchläuft. Es kann natürlich auch abstürzen, und spätestens, wenn mehr als einmal malloc ausgeführt wird (was üblicherweise der Fall ist), sind wir wieder bei Variablen, deren Werte sich aus nicht erkennbaren Gründen ändern und seltsamen Abstürzen in Bibliotheksfunktionen und anderen Heisenbugs.
Die Grundregel ist wirklich ziemlich simpel: Wenn du in Speicher schreibst, musst du sicherstellen, dass du genug davon hast. Unterlässt du das, erzeugst du undefiniertes Verhalten, was alles mögliche zur Folge haben kann und dementsprechend in zuverlässiger Software um jeden Preis zu vermeiden ist.
-
@kjhhjk
zugegeben wuste ich bis vor 3 tagen noch nicht mal, das mein Linux a C kann
hab ich auch nur mehr oder weniger durch zufall rausbekommen, als ich Linux für alle: C-Programmierung was geschaut habe.naja. wie dem auch sein.
da ich bisher noch nie mit C in berührung gekommen bin, hab ich auch keine Ahnung von dem entsprechenden Compiler gehabt, was sich zugegeben auch nicht ändert.
da ich aber keine lust habe, alle zeilen selber zu tippen, hab ich mir folgende Makefile angelegt.
CC = gcc PROG = inout all: $(CC) -c $(PROG).c $(CC) -o $(PROG) $(PROG).o clean: rm -f $(PROG) $(PROG).o *~Dane aber dennoch für den Hinweis
char *zeichen = malloc(sizeof(char) * (strlen(sValue) + 1));Das erspart mir schon erst mal die Probleme mit der Speicherverwaltung und euch unlästige Fragen.
