Problem bei return mit Char-Array

Hallo zusammen!
Ich will gerade ein kleines C-Programm schreiben das einen String verarbeitet und wie ihr vielleicht schon an meinem Quelltext seht programmiere ich noch nicht sehr lange in C.
Die erste Funktion macht aus einem Char einen Null-Terminierten String mit nur einem Zeichen. Das funktioniert auch einwandfrei.
Der zweiten Funktion wird in instring ein beliebiger String übergeben, von dem die letzten count Zeichen zurückgegeben werden sollen. Dazu lege ich eine Zählvariable und ein Char-Array an, wobei das Array als Buffer dient. Der Buffer wird initiiert und danach die Größe des Arrays auf die Ausgangsstringlänge angepasst. Wenn count Zeichen kopiert werden sollen, muss noch eins für die Null-Terminierung addiert werden. In einer for-Schleife von count bis zum Stringende werden dann die Zeichen von instring einzeln in den Buffer geschrieben. Am Ende steht auch in Buffer das richtige Ergebnis drin.
Das Problem macht der return-Befehl. Nach dem return bekomme ich folgende Fehlermeldung: „Run-Time Check Failure #2 - Stack around the variable ‚outstring‘ was corrupted.“.
Weiß jemand wo das Problem liegt? An anderen Stellen hat die Größenanpassung des Arrays wie hier gemacht auch funktioniert und ein Char-Array als Rückgabewert hat ja in der anderen Funktion auch getan. Die Visual C+±Hilfe hat mich leider nicht weitergebracht.
Quelltext:

#include
#include
#include
#include
#include

// …

char *terminatechar(char single_char){
char result[2];
result[0] = single_char;
result[1] = 0;
return result;
}

char *strright(char *instring, int count){
int x;
char outstring[1] = „“;
outstring[count + 1];
for(x = strlen(instring) - count; x

Oh Gott.

char *strright(char *instring, int count){
int x;
char outstring[1] = „“;

Das reserviert (auf den meisten Architekturen) genau ein Byte Speicher auf dem Stack. Daran kannst du nachher nichts mehr ändern.

outstring[count + 1];

Was das macht weiss ich nicht, jedenfalls sieht das nicht wirklich sinnvoll oder korrekt aus. Wenn das die Größe des Arrays nachträglich ändern soll: das geht nicht. Du musst dafür Platz auf dem Heap hernehmen (siehe malloc) oder einen passenden Puffer an die Funktion übergeben.

for(x = strlen(instring) - count; x

Hallo Nicos!
Danke erst Mal für deine Hilfe! Nachdem ich jetzt noch mal zu Strings in C nachgelesen habe, sehe ich meinen Fehler auch ein. Die Funktion strright habe ich darum umgeschrieben und jetzt funktioniert es auch! Ich verstehe aber zwei Dinge noch nicht ganz und würde mich freuen wenn du oder jemand anders im Forum sie mir erklären könnten. Hier aber erst Mal der Quelltext der beiden Funktionen:

#include
#include
#include
#include
#include

// …

char *terminatechar(char single_char){
char result[2];
result[0] = single_char;
result[1] = 0;
return result;
}

// Frage 2
char *strright(char *instring, const int count){
int x;
char *outstring = (char *) malloc(count + 1);
outstring[0] = 0; //Frage 1
for(x = strlen(instring) - count; x

Hallo Nicos!
Danke erst Mal für deine Hilfe! Nachdem ich jetzt noch mal zu
Strings in C nachgelesen habe, sehe ich meinen Fehler auch
ein. Die Funktion strright habe ich darum umgeschrieben und
jetzt funktioniert es auch! Ich verstehe aber zwei Dinge noch
nicht ganz und würde mich freuen wenn du oder jemand anders im
Forum sie mir erklären könnten. Hier aber erst Mal der
Quelltext der beiden Funktionen:

char *terminatechar(char single_char){
char result[2];
result[0] = single_char;
result[1] = 0;
return result;
}

Erstmal was ganz wichtiges: Diese Funktion erzeugt das Array „result“ auf dem Stack. Nach der Rückkehr aus der Funktion ist der aber nicht mehr gültig, d.h. das Array kann beliebig überschrieben werden.

char\* res = terminatechar( 'a' );
terminatechar( 'b' );
/\* Was steht jetzt in res? Im zweiten Aufruf ist wahrscheinlich
 der Stack vom ersten überschrieben worden, auf den verweist aber
 res... \*/

Frage 1 bearbeiten wir jetzt mal im Code.

char \*strright(char \*instring, const int count){
 int x;
 char \*outstring = (char \*) malloc(count + 1);
 /\* Das ist ein malloc. Wer macht das free? \*/

 outstring[0] = 0; //Frage 1
 /\* Besser: ... = '\0', braucht man aber nicht wirklich. \*/

 for(x = strlen(instring) - count; x 



> Aber warum funktioniert outstring = ""; nicht?


Damit weist du einfach nur der Variable outstring die Adresse eines leeren Strings zu, den der Compiler irgendwo im entsprechenden Segment anlegt. Diese Zuweisunk kopiert keinesfalls den Inhalt des angegebenen Strings in das existierende Array.



> Frage 2: Nicos du hast gemeint das man strcpy in einer Zeile  
> lösen könnte. Kannst du bitte posten wie das gehen soll. Ich  
> habe mich auch selbst daran versucht, bin aber kläglich  
> gescheitert.


strncpy( ziel, &some\_string[strlen(some\_string)-count], count );

Plus oder minus den üblichen off-by-one. Du musst nur dafür sorgen, dass "ziel" genug Speicher hat.

Hallo Nico!

Danke für deine schnelle Antwort!

Darf ich noch mal kurz wegen der terminatechar-Funktion nerven? Das von dir angesprochene Problem habe ich als nicht wichtig eingeschätzt, weil die Funktion das gemacht hat was ich erwartet hatte. Nachdem ich aber die von dir angesprochene Situation ausprobiert habe muss ich feststellen das dies eine große Dummheit war:
char *res = terminatechar(‚a‘);
printf(„Wert vom 1. Aufruf: %s\n“, res);
terminatechar(‚b‘);
printf(„Wert vom zweiten Aufruf: %s\n“, terminatechar(‚c‘));
Der Debugger sagt mir das nach dem ersten Aufruf von terminatechar mit „a“ auch wirklich das „a“ in res drin steht. printf in der zweiten Zeile hingegen gibt aber irgendeinen Blödsinn aus. Das gleiche zeigt dann auch der printf an, das terminatechar mit „c“ aufgerufen hat. Das in res irgendwas drin steht nachdem terminatechar das zweite Mal mit „b“ aufgerufen wurde ist mir klar, aber das beide printf’s so einen Blödsinn machen verstehe ich nicht. Zumal in der strright-Funktion terminatechar korrekt arbeitet.

Du hast gefragt wer in der strright-Funktion zu dem malloc das free macht. Nun nachdem outstring nur innerhalb von strright gültig ist wird doch der Speicher automatisch mit dem return wieder freigegeben dachte ich, oder? Deine stright Funktion mit dem strncpy-Befehl ist natürlich wesentlich eleganter als meine. Selber drauf gekommen wäre ich aber glaube ich nie. Genial! Du musst die Euphorie verstehen, - ich programmiere noch nicht so wirklich lange in C.

Grüße
C. Penkwitt

Hier aber erst Mal der Quelltext der beiden Funktionen:

char *terminatechar(char single_char){
char result[2];
result[0] = single_char;
result[1] = 0;
return result;
}

Erstmal was ganz wichtiges: Diese Funktion erzeugt das Array
„result“ auf dem Stack. Nach der Rückkehr aus der Funktion ist
der aber nicht mehr gültig, d.h. das Array kann beliebig
überschrieben werden.

char* res = terminatechar( ‚a‘ );
terminatechar( ‚b‘ );
/* Was steht jetzt in res? Im zweiten Aufruf ist
wahrscheinlich
der Stack vom ersten überschrieben worden, auf den verweist
aber
res… */

Frage 1 bearbeiten wir jetzt mal im Code.

char *strright(char *instring, const int count){
int x;
char *outstring = (char *) malloc(count + 1);
/* Das ist ein malloc. Wer macht das free? */

outstring[0] = 0; //Frage 1
/* Besser: … = ‚\0‘, braucht man aber nicht wirklich. */

for(x = strlen(instring) - count; x

Frage 2: Nicos du hast gemeint das man strcpy in einer Zeile
lösen könnte. Kannst du bitte posten wie das gehen soll. Ich
habe mich auch selbst daran versucht, bin aber kläglich
gescheitert.

strncpy( ziel, &some_string[strlen(some_string)-count], count
);

char *res = terminatechar(‚a‘);
printf(„Wert vom 1. Aufruf: %s\n“, res);
terminatechar(‚b‘);
printf(„Wert vom zweiten Aufruf: %s\n“, terminatechar(‚c‘));
Der Debugger sagt mir das nach dem ersten Aufruf von
terminatechar mit „a“ auch wirklich das „a“ in res drin steht.
printf in der zweiten Zeile hingegen gibt aber irgendeinen
Blödsinn aus.

Dafür musst du verstehen, wie der Stack funktioniert. Jedesmal, wenn eine Funktion aufgerufen wird, wird eine Basisadresse für den Stack gesetzt und die Funktion legt ab dieser Adresse ihre statischen Daten ab. Wenn diese Funktion nun beendet wird, so wird die Basisadresse wieder auf die der aufrufenden Funktion zurückgesetzt, die Daten bleiben aber noch erhalten, bis sie überschrieben werden, also bis wieder eine Funktion aufgerufen wird, die diesen Teil des Speichers für ihren Stack benötigt. printf() überschreibt den ehemaligen Stack von terminatechar(), so dass auch die Zeichenkette überschrieben wird. Daher kommt der Müll.

Du hast gefragt wer in der strright-Funktion zu dem malloc das
free macht. Nun nachdem outstring nur innerhalb von strright
gültig ist wird doch der Speicher automatisch mit dem return
wieder freigegeben dachte ich, oder?

Nein, der Speicherplatz wird auf dem Heap reserviert und erst wieder freigegeben, wenn free() mit der Basisadresse aufgerufen wird oder das Programm beendet wird. Speicherverwaltung ist eines der lustigsten Probleme in C.

Hallo Nicos!

Sorry das ich mich in meinem letzten Posting bei deinem Namen vertippt habe. Das war keine Absicht.

char *res = terminatechar(‚a‘);
printf(„Wert vom 1. Aufruf: %s\n“, res);
terminatechar(‚b‘);
printf(„Wert vom zweiten Aufruf: %s\n“, terminatechar(‚c‘));
Der Debugger sagt mir das nach dem ersten Aufruf von
terminatechar mit „a“ auch wirklich das „a“ in res drin steht.
printf in der zweiten Zeile hingegen gibt aber irgendeinen
Blödsinn aus.

Dafür musst du verstehen, wie der Stack funktioniert.
Jedesmal, wenn eine Funktion aufgerufen wird, wird eine
Basisadresse für den Stack gesetzt und die Funktion legt ab
dieser Adresse ihre statischen Daten ab. Wenn diese Funktion
nun beendet wird, so wird die Basisadresse wieder auf die der
aufrufenden Funktion zurückgesetzt, die Daten bleiben aber
noch erhalten, bis sie überschrieben werden, also bis wieder
eine Funktion aufgerufen wird, die diesen Teil des Speichers
für ihren Stack benötigt. printf() überschreibt den ehemaligen
Stack von terminatechar(), so dass auch die Zeichenkette
überschrieben wird. Daher kommt der Müll.

Wie müssten folgende Programmzeilen dann richtig heißen? Quasi so das nachher auch wirklich „a“ ausgegeben wird.
char *res = terminatechar(‚a‘);
printf(„Wert vom 1. Aufruf: %s\n“, res);

Du hast gefragt wer in der strright-Funktion zu dem malloc das
free macht. Nun nachdem outstring nur innerhalb von strright
gültig ist wird doch der Speicher automatisch mit dem return
wieder freigegeben dachte ich, oder?

Nein, der Speicherplatz wird auf dem Heap reserviert und erst
wieder freigegeben, wenn free() mit der Basisadresse
aufgerufen wird oder das Programm beendet wird.
Speicherverwaltung ist eines der lustigsten Probleme in C.

Das leuchtet mir noch nicht ein. Zumal wie soll ich den free-Befehl setzen? In outstring steht am Ende der Rückgabewert drin. Sobald ich aber zu einem return komme bin ich draußen aus der Funktion und kann kein free() mehr machen. Könnte das so gehen?
// … – nicht getestete Codesequenz –
char *buffer = outstring;
free(outstring);
return buffer;

Grüße
C. Penkwitt

Hallo Christian,

was m.E. noch nicht ganz klar ist, warum schreibst Du diese Funktionen, zur Übung, oder für echte Funktionalität in echten Programmen?

Ganz allgemein: Es ist m.E. besser, diese Funktionen im Einsatz immer ohne eigene Speicherallokierung zu machen. Wie selbst schon sagst, das Freigeben bereitet sonst nämlich große Probleme

// … – nicht getestete Codesequenz –
char *buffer = outstring;
free(outstring);
return buffer;

Das wird bei Dir tadellos funktionieren, ist aber genauso falsch wie die Stack-Routinen, da nach free der Speicher sofort von woander überschrieben werden kann (aber beim ersten testen nicht muss!)

nomal zu der Frage nach dem Warum.

Statt
char * res = terminatchar(‚a‘);
kannst Du auch immer schreiben:
char * res = „a“;

Da der String nicht const ist, kannst Du dieses a auch überschreiben:
res[0] = ‚b‘;
oder
res[0] = (char) (‚c‘ + 5) // gäbe dann ein „h“ - String

Deine Funktion terminatchar sollte dann den Pointer UND das Zeichen erhalten und dann einfach das Zeichen bei [0] im pointer einfügen. (Auch ein Makro (#define) wäre dazu gut.

WEnn Du für jeden String, den du verwenden willst ein entsprechendes
res1 = „a“;
res2 = „a“;
res3 = „a“;

kannst Du auch alle parallel bearbeiten und benutzen. Und wenn du diese Variablen local vor dem Aufruf der Funktion anlegst, musst Du Dir um Speicher-hohlen / freigeben keine gedanken machen.

Ähnlich sollte auch bei der zweiten Funktion der Speicher für den Outputstring vor dem Aufrufen der Funktion einfach auf dem Stack angelegt werden mit
char a[1000];
wenn Du bis zu 1000 Zeichen maximal verarbeiten willst. Diesen Speicher dann der Funktion mit übergeben, damit diese das Ergebnis einfach darein schreibt.

Gruß
achim

P.S.: malloc und free sind natürlich letztendlich die richtigen Funktionen, aber sie erfordern wirklich eiserne disziplin und einige Kenntnisse. Die Beschränkung auf den Stack und statische Variablen ist da fürs erste schon kompliziert genug.

Statt
char * res = terminatchar(‚a‘);
kannst Du auch immer schreiben:
char * res = „a“;

Da der String nicht const ist, kannst Du dieses a auch
überschreiben:
res[0] = ‚b‘;
oder
res[0] = (char) (‚c‘ + 5) // gäbe dann ein „h“ - String

Nein, das geht so nicht. Durch String-Konstanten können durchaus Referenzen in read-only Segmente gesetzt werden. Korrekterweise müsstest du veränderbare Strings entweder per

char a[1000] = "bar";

initialisieren, was den Speicher auf dem Stack reserviert, oder aber dynamisch allokieren und dann per str(n)cpy füllen. Wenn du einfach einen Pointer initialisierst passiert das oben erwähnte. (Als Übungsaufgabe: strcpy( „foo“, „bar“ ):wink:

Hallo Achim!

Vielen Dank für dein Posting! Bevor ich wieder mit nervigen Fragen komme will ich Antworten geben. Wozu das alles? An den Funktionsnamen kannst du vielleicht schon was erahnen. Peinlich aber wahr, ich komme aus der Visual Basic Welt. Dort habe ich einige Datenkompressionsroutinen geschrieben die gut funktionieren. C programmiere ich noch nicht so lange und mit Strings arbeite ich erst seit sehr kurzer Zeit. Ich habe aber die Vorteile von C in den anderen Bereichen zu Schätzen gelernt und will darum meine Datenkompressionsroutinen auf C ummünzen. Du fragst dich jetzt aber bestimmt immer noch nach dem Warum. Schließlich gibt es auch WinZIP, dass dies viel besser kann. Ich habe einfach Freude daran zu verstehen wie die Dinge funktionieren.
An dieser Stelle möchte ich dann auch auf deinen Vorschlag mit char a[1000] eingehen. Ich finde das nicht sehr elegant, da für a jetzt fast ein 1kB reserviert wurde und in a vielleicht nachher nur „Hallo“ drin steht. Andererseits kann es passieren das mein String viel länger als 1000 Zeichen ist. char a[wert] funktioniert auch nicht, da wert eine Konstante sein muss. Hingegen kann ich mit malloc ja scheinbar genau soviel Speicher reservieren wie ich brauche.
Je nachdem was für eine Datenkompressionsroutine man verwendet kann der komprimierte Text nachher nur um zwei Zeichen kürzer sein wie der Ausgangstext oder er kann auch über die hälfte Kürzer sein. Das kann ich nicht berechnen. In Basic hänge ich einfach den neuen Teilstring an den Buffer an und fertig.
Darum jetzt die Frage von mir anders formuliert (prinzipiell für alle Routinen die ich programmieren möchte): Eine Funktion der ein Char-Array sowie einige Integer-Datentypen übergeben werden soll einen String zurückgeben (in C nicht C++).
char *result = myfunction(„Bla Bla Bla“, 1, 2, 3);
Wenn ich nämlich char *myvar = “Hallo Welt!”; schreibe konnte ich bis jetzt immer – gemessen an meine C-Erfahrung – wieder mit myvar auf den String „Hallo Welt!“ zugreifen. Nun soll eben der Char-Pointer nicht mit einem von mir manuell eingegebenen Wert belegt werden, sondern mit dem Ergebnis der Funktion. Irdendwie muss das doch schon sauber zu programmieren gehen, oder?

Grüße
C. Penkwitt

Darum jetzt die Frage von mir anders formuliert (prinzipiell
für alle Routinen die ich programmieren möchte): Eine Funktion
der ein Char-Array sowie einige Integer-Datentypen übergeben
werden soll einen String zurückgeben (in C nicht C++).
char *result = myfunction(„Bla Bla Bla“, 1, 2, 3);
Wenn ich nämlich char *myvar = “Hallo Welt!”; schreibe konnte
ich bis jetzt immer – gemessen an meine C-Erfahrung – wieder
mit myvar auf den String „Hallo Welt!“ zugreifen. Nun soll
eben der Char-Pointer nicht mit einem von mir manuell
eingegebenen Wert belegt werden, sondern mit dem Ergebnis der
Funktion. Irdendwie muss das doch schon sauber zu
programmieren gehen, oder?

Du musst dir immer wieder klar machen, dass C eigentlich nur etwas besser lesbarer Assemblercode ist. Es macht genau das, was du sagst und erfindet wenig von sich aus dazu. Wenn du irgendwelchen Speicher brauchst, dann must du den irgendwo selber bereitstellen, sei das jetzt auf dem Stack oder im Heap. Das bedeutet, dass du dir für die Allokierung ein Schema überlegen und dich dann strikt daran halten musst um den Überblick zu behalten.

Wenn du eine Funktion hast, die als Ergebnis einen Pointer auf in der Funktion neu allokierten Speicher zurückgibt, dann musst du halt daran denken, dass der auch irgendwann wieder freigegeben werden muss. Insofern ist es „sauber“, wenn du dich an deine eigenen Konventionen hältst.

Schau dir als Beispiel mal die Funktionen der C-Bibliothek an. (Fast) keine dieser Funktionen allokieren in irgend einer Form über ihre eigene Lebenszeit hinaus Speicher, sondern sie erwarten einen Zeiger als Argument. Das führt dazu, dass sämtliche Speicherverwaltung im aufrufenden Code stattzufinden hat, wodurch man einigermaßen den Überblick behält. Wenn du eigene Funktionen schreibst, solltest du darauf achten, das ähnlich zu machen.

Wenn du absolute keine andere Möglichkeit hast, als Zeiger auf allokierten Speicher zurückzugeben, dann musst du halt jeden Aufruf der Funktion als malloc() verstehen und darauf achten, das Resultat solange es gültig und unverändert ist irgendwann durch free() zu schicken.

Von Fehlerbehandlung ist dabei noch garkeine Rede.

ein pragmatisches Allokieren
Hallo Christian,

neben den zahlreichen aufrufen von malloc besteht auch noch die möglichkeit, zu beginn des Programms, nach Kenntnis der maximalgrößen, ein oder 2 Puffer von Maximalgröße zu allocieren, und diese Puffer dann den entsprechenden Funktionen immer mitzugeben. also wie etwa

int main(int)
{
char str[MAX\_TEXTZEILE];
int size;

 ...
 read\_file();
 size = get\_size\_of\_file\_max();
 {
 char \*whole\_buf\_src = malloc(size);
 char \*whole\_buf\_dst = malloc(size + RESERVE);

 do\_whatever\_you\_want(whole\_buf\_src, whole\_buf\_dest, ...);
 } 
}

meist muss man sich eh von dem Gedanken lösen, nur ja kein Byte RAM ungenutzt zu lassen. Für eine Zeile ist es meist egal, ob Du 1000 Bytes reservierst, um 2 Byte zu übertragen, wenn im worst case wirklich 1000 Bytes möglich sind. Nur wenn Du Maximalgröße wirklich nur 2 Byte sind und tausende Puffer gleichzeitig aktiv sind, dann sollte man sich gedanken machen. Wenn der Puffer dagegen unendlich groß sein kann (oder z.B. 3 GByte), dann kommt evt. sogar nichtmal um eine Stückelung in maximale Brocken und Fileoperationen herum. Aber auch da gilt, dass meist ( wenigstens für den Anfang) ruhig geklotzt werden kann.

Über 100kByte Puffer zu Beginn statisch oder dynamisch allokiert lacht Dein rechner nur.

Gruß
achim

P.S.: VB klotzt in vielen Fällen auch so, nur macht es dass für Dich im Hintergrund. Aber es allokiert nicht byte für byte, nur weil Du einen String um 2 Zeichen verlängerst.

Du hast völlig recht
Hallo Nicos,

char * res = „a“;

Nein, das geht so nicht. Durch String-Konstanten können
durchaus Referenzen in read-only Segmente gesetzt werden.

Hallo Christian,

ich will mich mal verspäätet einmischen
und auch meinen Senf dazu abgeben.

Ich will gerade ein kleines C-Programm schreiben

… wird in instring ein beliebiger String
übergeben, von dem die letzten count Zeichen zurückgegeben
werden sollen. Dazu lege ich eine Zählvariable und ein
Char-Array an, wobei das Array als Buffer dient. Der Buffer
wird initiiert und danach die Größe des Arrays auf die
Ausgangsstringlänge angepasst. Wenn count Zeichen kopiert
werden sollen, muss noch eins für die Null-Terminierung
addiert werden. In einer for-Schleife von count bis zum
Stringende werden dann die Zeichen von instring einzeln in den
Buffer geschrieben. …

Genau dafür ist C eigentlich ziemlich ungeeignet,
wie das schon in anderen Beiträgen zum Ausdruck kam.
Um schon das sicher zu handhaben, brauchst Du
bereits große Erfahrung in C.

Hier wäre auf C++ und die String-Klasse zu verweisen,
die einem die komplizierte Handarbeit abnimmt.

Die Kombination aus „C“ und „Strings\0“ und Stackvariablen
ist für Anfänger nicht selten ein enormes Hindernis,
bis dann irgendwann die „Zeiger“ auftauchen :wink:

Grüße

CMБ

Hallo zusammen!
An dieser Stelle noch mal vielen Dank für eure Hilfe Nicos und Achim! Ich habe mir jetzt mal die Stringfunktionen wie von euch vorgeschlagen genauer angesehen und habe mir folgendes dazu überlegt:

  • Im Hauptprogramm reserviere ich mit malloc den Speicher für den Eingabestring und Ausgabestring. Die Größe des Ausgabestrings lege ich auf 1 fest.

  • Meiner Funktion übergebe ich dann die Speicheradresse der beiden Strings und die Anzahl der zu kopierenden Zeichen.

  • Die Funktion passt dann mit realloc die Größe des Ausgabestrings an.

  • Der return macht dann nur eine Aussage darüber ob alles geklappt hat oder nicht, aber das Ergebnis steht quasi im Übergabeparameter.
    Im Quelltext sieht das dann so aus:

    #include
    #include
    #include
    #include
    #include

    int strleft(char *outstring, char *instring, int count);

    int main(int argc, char *argv[]){

    char teststr[] = „Das ist ein kleiner Teststring.“;
    printf("[%i] %s\n", sizeof(teststr), teststr);

    char *outstring = (char *) malloc(1);
    printf(„sizeof oustring: %i\n“, sizeof(outstring));

    int res = strleft(outstring, teststr, 6);
    printf(„res = %i\n“, res);
    printf("[%i] %s\n", sizeof(outstring), outstring);

    free(outstring);

    return 0;
    }

    int strleft(char *outstring, char *instring, int count){

    if(count > strlen(instring)) return -1;

    realloc(outstring, count + 1);

    strncpy(outstring, instring, count);
    outstring[count] = 0;

    return 0;
    }

Das Programm gibt nachher an Text auch das aus was ich erwarte, aber die Größe von outstring passt irgendwie nicht wenn ich mir den Wert des sizeof-Befehls anschaue. Mit sizeof habe ich bis jetzt immer die exakte Größe des Arrays erhalten, aber hier erscheint ständig 4. Darum meine Frage, ob das so sauber programmiert ist (vor allem ob das mit dem realloc auch so funktioniert oder „Zufall“ ist) und warum der sizeof-Befehl nicht die richtige Größe ausgibt.

Grüße
C. Penkwitt

Das Programm gibt nachher an Text auch das aus was ich
erwarte, aber die Größe von outstring passt irgendwie nicht
wenn ich mir den Wert des sizeof-Befehls anschaue. Mit sizeof
habe ich bis jetzt immer die exakte Größe des Arrays erhalten,
aber hier erscheint ständig 4. Darum meine Frage, ob das so
sauber programmiert ist (vor allem ob das mit dem realloc auch
so funktioniert oder „Zufall“ ist) und warum der sizeof-Befehl
nicht die richtige Größe ausgibt.

sizeof(outstring) stellt die Größe von outstring fest. Diese Variable hat den Typ „char*“, ist also ein Zeiger. Auf 32bit-x86-Rechnern belegt ein Zeiger 32bit, also 4 Byte. Die Größe eines dynamischen Arrays festzustellen ist mit „Bordmitteln“ praktisch unmöglich, du müsstest dir also bei jeder (re)allokation die Größe merken.

Das realloc so funktioniert ist mehr oder weniger Zufall. Der Zeiger auf den vergrößerten Speicherbereich wird als Rückgabewert übergeben, du hast nur bisher das Glück gehabt, dass der Speicherblock nicht an eine andere Adresse verlegt werden musste. Korrekt müsste es heißen:

outstring = realloc(outstring, count + 1);

Außerdem kann es bei jedem malloc oder realloc passieren, dass kein Speicher mehr zur Verfügung steht oder sonstwas schiefgeht, so dass NULL zurückgegeben wird. Das solltest du nach jedem Aufruf prüfen und angemessen handhaben.

Hallo zusammen!

wie Nico schon schreibt ist malloc und realloc nichts fürs tägliche hantieren.

Was spricht dagegen, den maximalen Puffer für outstring in der aufrufenden Funktion zu ermitteln (oder fest auf 100kByte zu setzen). Es speilt dabei keine Rolle, wenn er z.B. 3 mal größer ist, als er vielleicht nur sein soll.

Eine Bearbeitung z.B. von strcpy kopiert ein paar Zeichen von Speicher zu Speicher (in der Regel sind es 3 Zeilen Code ohne Unterfunktion). Ein Aufruf von malloc ist dagegen ein schweres Geschütz, mit vielen möglichen Seiteneffekten. Auch wenn Du nur ein Byte haben willst, kannst Du sicher sein mehrere kByte direkt oder indirekt zu blockieren. Ganz zu schweigen von der nervigen Abfrage ob es geklappt hat, und vor allem der sinnvollen reaktion wenns schiefläuft.

Gruß
achim

Hallo Nicos!
OK, das mit den 4Byte habe ich verstanden. Jetzt noch eine aller letzte Frage:
Wenn ich outstring = realloc(outstring, count + 1) in der Funktion strleft aufrufe, verändere ich damit auch die Größe von outstring in der main-Funktion (vgl. dazu Posting von mir mit dem Gesamtquelltext)? Denn ich habe ja ohne das „&“ die Funktion aufgerufen. Ich gehe mal weiter von dem fiktiven Fall aus, das ich zwei Variablen habe und die wie folgt im Speicher liegen:

| - - var1 - - | - - NULL \* - - | - - var2 - - |

* Das soll heißen das dieser Speicherbereich gerade ungenutzt ist

var1 = realloc(var1, 1000);

Lass uns auch weiter davon ausgehen das der Bereich NULL des freien Speichers kleiner als 1000 ist. Was passiert dann?
An dieser Stelle möchte ich dir und auch Achim noch Mal ganz herzlich für eure Hilfe danken. Um C zu lernen habe ich mir die Bücher „C entpackt“ und „Softwaretechnik in C und C++“ besorgt. Einige Dinge habe ich dort gar nicht erklärt gefunden und einige bei weitem nicht so gut wie ihr sie erklärt habt! Danke!

Grüße
C. Penkwitt

sizeof(outstring) stellt die Größe von outstring fest. Diese
Variable hat den Typ „char*“, ist also ein Zeiger. Auf
32bit-x86-Rechnern belegt ein Zeiger 32bit, also 4 Byte. Die
Größe eines dynamischen Arrays festzustellen ist mit
„Bordmitteln“ praktisch unmöglich, du müsstest dir also bei
jeder (re)allokation die Größe merken.

Das realloc so funktioniert ist mehr oder weniger Zufall. Der
Zeiger auf den vergrößerten Speicherbereich wird als
Rückgabewert übergeben, du hast nur bisher das Glück gehabt,
dass der Speicherblock nicht an eine andere Adresse verlegt
werden musste. Korrekt müsste es heißen:

outstring = realloc(outstring, count + 1);

Außerdem kann es bei jedem malloc oder realloc passieren, dass
kein Speicher mehr zur Verfügung steht oder sonstwas
schiefgeht, so dass NULL zurückgegeben wird. Das solltest du
nach jedem Aufruf prüfen und angemessen handhaben.

Wenn ich outstring = realloc(outstring, count + 1) in der
Funktion strleft aufrufe, verändere ich damit auch die Größe
von outstring in der main-Funktion (vgl. dazu Posting von mir
mit dem Gesamtquelltext)? Denn ich habe ja ohne das „&“ die
Funktion aufgerufen.

Das wirkt sich dann nur auf das „lokale“ outstring in der Funktion aus. Um den Wert auch in main zu ändern müsstest du die Signatur von strleft ändern:

strleft( char &\*outstring, ... )

und dann in der Funktion entsprechend derefenenzieren. (Das geht in C++ dank Referenzen einfacher…)

Lass uns auch weiter davon ausgehen das der Bereich NULL des
freien Speichers kleiner als 1000 ist. Was passiert dann?

Wenn für das realloc der freie Speicher „hinter“ dem ursprünglichen Block nicht ausreicht, dann wird ein neuer Block allokiert, der Inhalt kopiert und der alte freigegeben. Deshalb der neue Zeiger als Rückgabewert. Irgendwo solltest du Dokumentation zu den Funktionen haben in der das erklärt wird, realloc() hat nämlich nebenbei noch die nette Möglichkeit, NULL zurückzugeben und dabei den alten Zeiger gültig zu lassen, d.h. du solltest den Wert sogar über eine temporäre Variable abholen und erstmal prüfen.

Hallo C.

Dein (denk)Fehler liegt bei „sizeof()“.

sizeof() ist keine Funktion, sondern wird vom Compiler ausgewertet !!
Mit sizeof() kannst du abfrage wieviel Speicher der Compiler für irgendetwas reserviert.
Bei C gibt es diverse Dinge welche von der verwendeten Implementierung (CPU) abhängen, z.B.:

struct
{
char c;
int i;
} s_demo;

sizeof(s_demo) liefert, je nach Implementierung, wahrscheinlich irgendetwas zwischen 3 und 16.

Wenn char 8Bit und int 16Bit gross ist und die CPU auch 16Bit Zugriffe auf ungerade Adressen erlaubt ist das Resultat 3.
Erlaubt die CPU nur 16Bit Zugriffe auf Gerade Adressen, oder wird der Code z.B. für eine 80x86-CPU optimiert, muss zwischen cchar und int noch ein Füllbyte eingeschoben werden, der Speicherbedarf ist dann 4 Byte.
Wird Unicode als Zeichensatz verwendet ist char 16Bit lang und int darf auch 32 oder 64 oder sonst was lang sein…
Die Anzahl der Füllbytes hängt auch von der CPU und der Optimierung ab.

Wenn du nun z.B. mit malloc() Speicher allozieren willst musst du aber die genaue Grösse kennen. Deshalb empfiehlt es sich immer
x = malloc(sizeof(s_demo));
zu verwenden. Dieses Konstrukt funktioniert dann mit jedem Compiler.

Wenn du wissen willst wie lange eine Zeichenkette ist musst du strlen() verwenden.

MfG Peter(TOO)