chars, strings, malloc (?) Problem
-
Hallo zusammen,
vielleicht ist das Problem hier super einfach, und ich sehe nur den Wald vor lauter Bäumen nicht mehr, aber ich komme hier einfach nicht weiter.
Folgendes Problem: Ich muss char-Weise Zeichen von der Konsole einlesen, und diese dann zu Strangs zusammensetzten (das ganze soll nämlich dynamisch groß sein, folglich kann ich keine Zeilenpuffer, o.Ä. verwenden, weil die ja immer eine fixe Größe haben).
Das Programm funktioniert wie folgt wunderbar:
int main(int argc, char **argv, char **envp){ char c = '\0'; char *input = malloc(sizeof(char)); char *tmp; int new_size; printf("%s", ps1); while(c != EOF){ c = getchar(); switch(c){ case '\n': printf("%s",ps1); input_parse(input); input = malloc(sizeof(char)); break; default: new_size=sizeof(*input) + sizeof(char*); tmp = malloc(new_size); strcpy(tmp, input); input = malloc(new_size); strncat(tmp, &c, 1); strcpy(input, tmp); break; } } return 0; }
Nun wollte ich das ganze in eine extra Funktion auslagern, und seit dem kracht es. Hier zunächst einmal die Funktion ohne printf-Debugging-Ausgaben:
int main(int argc, char **argv, char **envp){ char c = '\0'; char *input = malloc(sizeof(char)); printf("%s", ps1); while(c != EOF){ c = getchar(); attach_char_to_cmd(c, input); } return 0; } int attach_char_to_cmd(char c, char *input){ char *tmp; int new_size; switch(c){ case '\n': input_parse(input); input = malloc(sizeof(char)); printf("%s",ps1); break; default: new_size = sizeof(*input) + sizeof(char*); tmp = malloc(new_size); strcpy(tmp, input); input = malloc(new_size); strncat(tmp, &c, 1); strcpy(input, tmp); break; } return 0; }
Der Code kompiliert zwar, aber blöderweise bleibt "input" leer. Interessanterweise funktioniert hier aber auch der Malloc-Aufruf scheinbar nicht mehr. Hier noch einmal den Code mit ein paar printf-Ausgaben sowie die dazugehörige Ausgabe:
printf("Eingaben sind: c = %c, input = %s\n", c, input); switch(c){ case '\n': input_parse(input); input = malloc(sizeof(char)); printf("%s",ps1); break; default: printf("In the beginning there was: c = %c, input = %s\n", c, input); printf("Sizes: tmp = %i, input = %i\n", sizeof(tmp), sizeof(input)); new_size = sizeof(*input) + sizeof(char*); printf("Then came new size= %i\n", new_size); tmp = malloc(new_size); printf("Assigned tmp the new size: %i\n", sizeof(tmp)); strcpy(tmp, input); printf("Copied input to tmp: %s\n", tmp); input = malloc(new_size); printf("New Size of input: %i\n", sizeof(input)); strncat(tmp, &c, 1); printf("New String of tmp: %s\n", tmp); strcpy(input, tmp); printf("Copied String to input: %s\n", input); break;
krah_kth sh %> ls
Eingaben sind: c = l, input =
In the beginning there was: c = l, input =
Sizes: tmp = 4, input = 4
Then came new size= 5
Assigned tmp the new size: 4
Copied input to tmp:
New Size of input: 4
New String of tmp: l
Copied String to input: l
Eingaben sind: c = s, input =
In the beginning there was: c = s, input =
Sizes: tmp = 4, input = 4
Then came new size= 5
Assigned tmp the new size: 4
Copied input to tmp:
New Size of input: 4
New String of tmp: s
Copied String to input: s
Eingaben sind: c =
, input =
krah_kth sh %>Nach meinem Verständnis müsste der Code so eigentlich laufen, denn Strangs werden ja als Array von Chars übergeben, und Arrays wiederum werden nicht als Wertekopie an die Funktion überreicht, sondern eben als Referenz. Mit
input = malloc(new_size)
weise ich also dem input-pointer einen neuen Speicherbereich zu, auf dem dann von überall aus zugegriffen werden kann. Und dann kopiere ich die Strings wieder um. Soweit ich das überblicken kann, scheint das ja auch zu funktionieren. Das Problem wird wohl das nicht-funktionierende malloc() sein, wie diese Zeile zeigt:
Then came new size= 5
Assigned tmp the new size: 4Weiß hier jemand rat?
-
Da sind schon im ersten Code Fehler. Der sollte auch nicht funktionieren, zumindest nicht lange (ist bei undefiniertem Verhalten eben schwer vorher zu sagen):
-sizeof(char) ist 1, immer.
-sizeof(*input): Nun, input ist ein char*, daher ist *input ein char, daher ist sizeof(*input) 1, immer. Vielleicht suchst du strlen. Oder besser: Merk dir die Größe doch.
-Der Sinn der Übung besteht ja wohl kaum da drin, Speicherlöcher zu erzeugen. Du gibst den Speicher nie frei, stattdessen überschreibst du den einzigen Pointer den du hast, so dass du an den reservierten Speicher nie wieder dran kommst!
-Außerdem fehlt dir immer die Nullterminierung!Die anderen beiden Codes habe ich mir jetzt nicht angesehen, du hast mit den vier Punkten erst einmal genug zu tun.
Noch ein Tipp: Code compiliert != Code ist richtig. Ganz besonders nicht in C, wo so ziemlich alles compiliert, weil angenommen wird, dass der Programmierer genau weiß, was er tut. Ist dann eben schlecht, wenn er es nicht weiß. Dreh daher die Warnoptionen deines Compilers so weit auf wie es geht und behandle jede Warnung wie einen Fehler.
-
Zu deinem Funktionsproblem:
Du änderst in deiner Funktion die Adresse der Speicherstelle von deinem String.
Dies gibst du aber nicht anmain()
zurück.
main()
übergibt jedesmal anattach_char_to_cmd()
den ursprünglichen Wert.
attach_char_to_cmd()
holt sich neuen Speicher und schreibt die Daten dahin.Man kann mit %p bei
printf()
auch Pointer ausgeben.
Mach mal vor dem switch und vor return:printf("input vor switch: %p: %s\n", input, input);
-
Mal etwas salopp gesprochen:
--> Die Konsole ist gewöhnlich ein endliches Gerät mit endlich vielen potentiellen Characters als Eingabe.
--> Speicherplatz muss mit und ohne malloc genug vorhanden sein.
--> Häufige dynamische Anforderungen (und Freigaben) von Speicher erhöhen die Systembelastung und sind unübersichtlich.
--> C ist anfällig bei Bereichsüberschreitungen, aber deshalb braucht man nicht notwendig überall malloc.
--> Was spricht gegen einen Buffer ausreichender Grösse für alles, z.B. char buffer[4096]?
--> Für grössere Dinge wie ganze Bücher (e-book) verwendet man ohnehin besser vorhandene Standardsoftware.
--> Programmieren heisst auch, sich das Leben so einfach wie möglich zu machen und mehr auf die Stabilität des Programmes zu achten.
--> Was ist das Ziel?
-
Danke euch für die Hinweise. Das ganze hat mich jetzt schon sehr viel weiter gebracht. Vielen Dank!
@seppJ:
Das mit dem Speicher war mir durchaus bewusst, allerdings wollte ich erst einmal das Ding generell zum laufen bekommen, um mir dann die beste Stelle für das wieder freigeben des Speichers zu suchen. Davor hat es immer mal wieder gekracht, sobald ich ein wenig was geändert hatte, daher hab ich die free() Anweisungen erst einmal komplett außen vor gelassen.@berniebutt:
Ziel und zweck des ganze ist es, ein wenig C zu lernen (das sind meine ersten Gehversuche in C - davor hab ich nur objektorientiert in Java und Smalltalk programmiert) und in Betriebssystem-Programmierung und Betriebssysteme allgemein einzusteigen. Sicherlich hätte ich mir die Arbeit auch ein wenig leichter machen können. Aber ich wollte einfach mal ausprobieren, wie sich ein wachsendes Array für (theoretisch) unendliche Eingaben realisieren lässt.Nur der Vollständigkeit halber, falls es jemanden interessiert: So sieht der Code jetzt aus, und er läuft sehr gut und ohne Fehler, soweit ich das beurteilen kann.
int main(int argc, char **argv, char **envp){ /* Declaring and initializing variables */ char c = '\0'; char *input = malloc(1); printf("%s", ps1); while(c != EOF){ c = getchar(); char_count++; input = attach_char_to_cmd(c, input, &char_count); } return 0; } char *attach_char_to_cmd(char c, char *input, int *char_count){ char *tmp; tmp = malloc(*char_count); strcpy(tmp, input); free(input); input = malloc(*char_count); switch(c){ case '\n': c='\0'; if(*char_count != 1){ strncat(tmp, &c, 1); strcpy(input, tmp); input_parse(input); } *char_count = 0; free(input); input = malloc(1); printf("%s",ps1); break; default: if(*char_count == 1) strcpy(tmp, &c); else strncat(tmp, &c, 1); strcpy(input, tmp); break; } free(tmp); return input; }
-
Das ist immer noch falsch. Immer noch keine Nullterminierung, obwohl du Funktionen aus string.h benutzt, die dies so erwarten. Immer noch veränderst du lokale Variablen und erwartest, dass die Änderung nach draußen getragen wird.
Wenn das nur zu deiner eigenen Übung ist, mach erst einmal etwas leichteres.
-
Hi,
woher kommt "char_count" und "ps1"? Ist die Variable global deklariert? Dann brauchst du dazu aber eigentlich nicht die Adresse rumschieben.
tmp = malloc(*char_count); strcpy(tmp, input); free(input); input = malloc(*char_count);
Was machst du da? Du schreibst einen String in einen zuvor allocierten Speicherbereich, um danach den ursprünglichen Speicherplatz mit free und malloc "neu anzulegen". Vllt. interessiert dich da ja realloc().
-
Das mit dem realloc war ein toller Tipp. Ja, das macht es um einiges einfacher. Und was die Strings anbelangt, wollte ich jetzt bei jedem Schritt nach dem erweitern ein '\0' an die letzte Stelle des Arrays schreiben - das fand ich aber irgendwie zu doof, und hab da jetzt sprintf() für verwendet (was mich irritiert: Weder mit -Wall noch mit -pedantic kreidet gcc mir das mit den Nicht-0-terminierenden Strings an (Nur meine C++ Kommentare im C Code findet -pedantic nicht so gut)).
So sieht das ganze jetzt aus:
char *attach_char_to_cmd(char c, char *input, int *char_count){ input = (char*) realloc(input, (*char_count+1)); switch(c){ case '\n': c='\0'; if(*char_count != 1){ sprintf(input, "%s%c", input, c); input_parse(input); } *char_count = 0; input = (char*) realloc(input, 1); input = '\0'; printf("%s",ps1); break; default: sprintf(input, "%s%c", input, c); break; } return input; }
SeppJ schrieb:
Immer noch veränderst du lokale Variablen und erwartest, dass die Änderung nach draußen getragen wird.
Da bin ich jetzt verwirrt. Wo mache ich denn das noch? Zurückgegeben werden muss input, das wird es auch. Der Char c wird nicht verändert und soll nachher auch verworfen werden, tmp wird nicht außerhalb benötigt, und char_count wird verändert, und daher hantiere ich hier mit einem Pointer auf die integer variable. Das Funktioniert auch, in der Aufrufenden Schleife ist der Wert nach Durchlauf des '\n'-Cases beim Switch auch wieder auf 0 gesetzt.
@DaRe: Die pst ist in der Tat global definiert, die char_count ist lokal in der Main definiert, allerdings ist das wohl beim Copy&Pasten verloren gegangen... Verzeihe bitte.
-
Was passiert, wenn realloc keinen Speicher mehr bekommt?
Es gibt NULL zurück und dann ist dein bisheriger Daten auch weg.Im Falle von '\n' setzt du c auf 0.
Dann ist dassprintf()
danach auch sinnlos.
Zudem iststrcpy()
allemal besser als dassprintf()
.Und wozu überhaupt char_count, dafür gibt es
strlen()
.
-
edit: Doch nicht. Dein Code ist nur sehr unüblich geschrieben, daher war ich verwirrt.
-
int attach_char_to_cmd(char *input[], char c) { int char_count = 2; char *neubuf = NULL; if(!isprint(c)) { // nichts anhaengen if(input != NULL) return (int)strlen(*input); else return 0; } if(input != NULL) { char_count += strlen(*input); neubuf = (char*)malloc(char_count); sprintf(neubuf, "%s%c", *input, c); free(input); } else { neubuf = (char*)malloc(char_count); sprintf(neubuf, "%c", c); } } input = neubuf; return char_count; }
1. Die unnützen Parameter und die fehlende Absicherung bei
Nullpointern würde man mit diesem Ansatz vermeiden.2. Problem: '\n' ist auf verschiedenen Systemen unterschiedlich
kodiert - unter Windows besteht es aus zwei Byte!! Es empfielt sich
daher eine Hilfsfunktion wie isprint();http://www.marquardtnet.info/cecke/quickies.1/1_quicky_27.html