Forum: PC-Programmierung String-Array und malloc


von Daniel S. (dani2304)


Lesenswert?

Hallo,
ich will einen String-Array mit einer dynamischen Speicher verwalten.
1
int main()
2
{
3
    int laenge;
4
    char *str;
5
    char ein[200];
6
    fgets(ein, 200, stdin);
7
    laenge=strlen(ein);
8
    str=malloc(sizeof(char*)*laenge);
9
    strcpy(str,ein);
10
    printf("%s",str);
11
}
Muss ich da wirklich über eine Zwischenvariable (hier "ein") gehen oder 
geht das auch anders?

MfG

von Dumdi D. (dumdidum)


Lesenswert?

- in sizeof den * löschen, und +1 bei laenge in malloc schreiben
- ein free fehlt
- du könntest in dem Beispiel str weglassen, ob Du 'ein' weglassen 
kannst hängt davon ab was Du wirklich vorhast (vermutlich nicht)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Falls du auf einem Posix-kompatiblen System arbeitest, kannst du dir
auch mal getline() ansehen.  Das ist (optional) in der Lage, den
Speicher selbst passend zu allozieren.

von Jobst Q. (joquis)


Lesenswert?

Du brauchst auf jeden Fall einen Puffer, um mit fgets etwas einzulesen. 
'ein' ist in diesem Fall der Puffer.

Du könntest auch mit str=malloc(200) vorher einen Puffer anlegen, in den 
eingelesen wird, und dann mit str=realloc(str,strlen(str)+1) auf den 
wirklich benötigten Speicher kürzen.

Das malloc(sizeof(char*)*laenge)ist übrigens Unsinn, da du nicht Länge 
mal Zeiger auf Char brauchst, sondern Länge+1. sizeof(char) ist 1, 
deshalb kannst du es weglassen, und 1 Byte brauchst du noch für das 
Nullbyte am Ende.

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

Und bedenke, das fgets auch das '\n' von der Entertaste mit im String 
ablegt.

von Jobst Q. (joquis)


Lesenswert?

Daniel S. schrieb:
> ich will einen String-Array mit einer dynamischen Speicher verwalten

Was du bisher hast, ist aber kein Stringarray, sondern ein String bzw 
ein Char-array. Ein Stringarray ist ein Array von Strings, also von 
Zeigern auf char.

Falls du das vorhast, ist ein lokaler Puffer wie 'ein' doch günstiger. 
Das Kopieren, strlen und malloc lässt sich durch strdup ersetzen und 
damit vereinfachen: str_array[i]=strdup(ein);

: Bearbeitet durch User
von Daniel S. (dani2304)


Lesenswert?

Jobst Q. schrieb:
> Du brauchst auf jeden Fall einen Puffer, um mit fgets etwas einzulesen.
> 'ein' ist in diesem Fall der Puffer.
>
> Du könntest auch mit str=malloc(200) vorher einen Puffer anlegen, in den
> eingelesen wird, und dann mit str=realloc(str,strlen(str)+1) auf den
> wirklich benötigten Speicher kürzen.
>
> Das malloc(sizeof(char*)*laenge)ist übrigens Unsinn, da du nicht Länge
> mal Zeiger auf Char brauchst, sondern Länge+1. sizeof(char) ist 1,
> deshalb kannst du es weglassen, und 1 Byte brauchst du noch für das
> Nullbyte am Ende.


Okay und gibt es zufällig noch eine bessere Möglichkeit als einen string 
zu verschieben als:
1
int main()
2
{
3
    int i;
4
    char *str;
5
    str=malloc(200);
6
    fgets(str, 200, stdin);
7
    str=realloc(str,strlen(str)+1);
8
9
    for(i=0 ;i<20;i++){              // Nur Ein Beispiel mit dem Index 5!
10
        str[5+i]=str[5+i+1];
11
    }
12
    printf("%s",str);
13
14
15
}

von Dirk B. (dirkb2)


Lesenswert?

Du kannst dir in einem anderen Pointer den Anfang merken.
str brauchst du weiterhin für das free.

char *str5 = str+5;

oder du nimmst die Standardfunktion memmove.
Die kommt auch mit überlappenden Bereiche klar.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Daniel S. schrieb:
> str=realloc(str,strlen(str)+1);

So sollte man realloc übrigens nie aufrufen.

Wenn nicht mehr genügend Speicher verfügbar ist, liefert realloc NULL, 
ohne den Speicher zu verändern, auf den das erste Argument zeigt.

Weist man den Rückgabewert von realloc aber dem Pointer zu, der auf 
den alten Speicherblock zeigt, ist damit der alte Speicherblock in den 
ewigen Datengründen verschwunden. Man kann den Speicherblock nicht mehr 
freigeben, und man kann nicht mehr auf die enthaltenen Daten zugreifen.

Daher:
Das Ergbnis von realloc immer einem (temporären) Hilfspointer 
zuweisen.

von Jobst Q. (joquis)


Lesenswert?

for(i=0 ;i<20;i++){              // Nur Ein Beispiel mit dem Index 5!
        str[5+i]=str[5+i+1];
        }


Das darfst du nur machen, wenn die Länge des Strings > (20+5+1) ist, 
weil ja nur die Länge des Strings reserviert ist.

Mit dem lokalen Puffer ein[200] wär das kein Problem. Wenn du das 
realloc weglässt, auch nicht, dann wären ja auch 200 Bytes reserviert.


Wenn du nur nach vorne schieben willst, geht dasselbe auch mit strncpy.

strncpy(str+5,str+6,20);

Wenn der ganze Reststring (incl \0)nach vorn verschoben werden soll:

strcpy(str+5,str+6);

: Bearbeitet durch User
von Dumdi D. (dumdidum)


Lesenswert?

Um zu Deiner Ausgangsfrage zurückzukomnen: falls man die Summe aller 
Stringlängeb in voraus kennt (z.B. durch Dateigrösse) reicht 1 malloc 
aus. Und dann wird mit fgets in diesen Speicher eine Zeile eingelesen, 
und ein neuer (zusätzlicher) Pointer auf das Ende der Zeile erzeugt, bis 
der allokierte Speicher voll und die Datei vollständig eingelesen ist.

von Daniel S. (dani2304)


Lesenswert?

Dumdi D. schrieb:
> Um zu Deiner Ausgangsfrage zurückzukomnen: falls man die Summe aller
> Stringlängeb in voraus kennt (z.B. durch Dateigrösse) reicht 1 malloc
> aus. Und dann wird mit fgets in diesen Speicher eine Zeile eingelesen,
> und ein neuer (zusätzlicher) Pointer auf das Ende der Zeile erzeugt, bis
> der allokierte Speicher voll und die Datei vollständig eingelesen ist.


Okay perfekt das werde ich gleich mal probieren!
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
int main()
6
{
7
    int laenge,i;
8
    char *str;
9
    str=malloc(200);
10
    fgets(str, 200, stdin);
11
    str=realloc(str,strlen(str)+1);
12
    laenge=strlen(str);
13
    for(i=0;i<laenge;i++){ //Verschieben des strings, wenn str[i] kein Buchstabe ist
14
        while(!((str[i]>='a' && str[i]<='z') || (str[i]>='A' && str[i]<='Z'))){
15
            strcpy(str+i,str+i+1);
16
        }
17
    }
18
    printf("%s",str);
19
}

Wieso verlässt er die while Schleife nicht? Eigentlich müsste er doch so 
lange den String verschieben bis ein Buchstabe kommt und dann müsste die 
while-schleife != 0 sein. Oder übersehe ich da was?

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

strcpy() darf meiner Erinnerung nach keine überlappenden
Speicherbereiche bekommen.  Sowas darf man nur mit memmove() machen.

von Jobst Q. (joquis)


Lesenswert?

Das kommt auf die Richtung an. Wenn das Ziel hinter der Quelle ist, geht 
es natürlich nicht, da die Quelle teilweise überschrieben wird, bevor 
sie gelesen wird.

Aber strcpy müsste schon ziemlich verrückt und umständlich implementiert 
sein, wenn man damit nicht von hinten nach vorne kopieren könnte.

Bei memmove wird eben vorher die Richtung ermittelt, in der kopiert 
werden darf.

: Bearbeitet durch User
von guest (Gast)


Lesenswert?

Dumdi D. schrieb:
> Um zu Deiner Ausgangsfrage zurückzukomnen: falls man die Summe aller
> Stringlängeb in voraus kennt (z.B. durch Dateigrösse) reicht 1 malloc
> aus. Und dann wird mit fgets in diesen Speicher eine Zeile eingelesen,
> und ein neuer (zusätzlicher) Pointer auf das Ende der Zeile erzeugt, bis
> der allokierte Speicher voll und die Datei vollständig eingelesen ist.

Wenn man die Gesamtgröße kennt reicht zwar 1 malloc, aber das Lesen mit 
fgets ist dann eher kontraproduktiv. Besser man liest mit fread die 
komplette Datei in einem Rutsch in den Buffer und sucht anschließend mit 
strchr nach den Zeilenenden. Das ist deutlich schneller.

von guest (Gast)


Lesenswert?

Daniel S. schrieb:
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
>
> int main()
> {
>     int laenge,i;
>     char *str;
>     str=malloc(200);
>     fgets(str, 200, stdin);
>     str=realloc(str,strlen(str)+1);
>     laenge=strlen(str);
>     for(i=0;i<laenge;i++){ //Verschieben des strings, wenn str[i] kein
> Buchstabe ist
>         while(!((str[i]>='a' && str[i]<='z') || (str[i]>='A' &&
> str[i]<='Z'))){
>             strcpy(str+i,str+i+1);
>         }
>     }
>     printf("%s",str);
> }
>
> Wieso verlässt er die while Schleife nicht? Eigentlich müsste er doch so
> lange den String verschieben bis ein Buchstabe kommt und dann müsste die
> while-schleife != 0 sein. Oder übersehe ich da was?

Für jedes Zeichen, daß kein Buchstabe ist immer den kompletten String um 
ein Zeichen zu verschieben ist ziemlich ungünstig. Überleg mal was 
passiert, wenn am Anfang erstmal 50 Ziffern kommen :(.
Und da durch die Verschieberei der benutzte Teil des Buffers immer 
kleiner wird, wäre es auch besser das realloc erst danach zu machen.
Und irgendwann den allokierten Speicher wieder freizugeben ist auch 
keine schlechte Idee.
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
#include <ctype.h>
5
6
int main()
7
{
8
    int laenge;
9
    char *str;
10
    char *p;
11
    str=malloc(200);
12
    fgets(str, 200, stdin);
13
    p = str;
14
    while( *p && !iasalpha(*p) )
15
    {
16
        p++;
17
    }
18
    laenge=strlen( p ) + 1;
19
    memmove( str, p, laenge );
20
    p = realloc( str, laenge );
21
    if( p )
22
    {
23
        str = p;
24
    }
25
    printf( "%s", str );
26
    free( str );
27
}

von guest (Gast)


Lesenswert?

Ooops, da war ein 'a' zuviel 'iasalpha' --> 'isalpha'.

Hier noch eine Variante
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
#include <ctype.h>
5
6
int main()
7
{
8
    int laenge;
9
    char *str;
10
    char *p;
11
    str=malloc(200);
12
    fgets(str, 200, stdin);
13
    if( isalpha(*str) )
14
    {
15
        laenge=strlen( str ) + 1;
16
    }
17
    else
18
    {
19
        p = str;
20
        do
21
        {
22
            p++;
23
        } while( *p && !isalpha(*p) );
24
        laenge=strlen( p ) + 1;
25
        memmov( str, p, laenge );
26
    }
27
    p = realloc( str, laenge );
28
    if( p )
29
    {
30
        str = p;
31
    }
32
    printf( "%s", str );
33
    free( str );
34
}

von Dirk B. (dirkb2)


Lesenswert?

Jobst Q. schrieb:
> Aber strcpy müsste schon ziemlich verrückt und umständlich implementiert
> sein, wenn man damit nicht von hinten nach vorne kopieren könnte.

Du hast aber keinen Einfluß darauf, wie es implementiert ist.
Und es ist nicht vorgesehen, dass sich die Bereiche überlappen.

Es kann jetzt gut gehen, aber mit einem anderen System/Compiler/Version 
muss es nicht mehr funktionieren.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dirk B. schrieb:
> Es kann jetzt gut gehen, aber mit einem anderen System/Compiler/Version
> muss es nicht mehr funktionieren.

Schlimmer noch: keiner sagt, dass der Compiler überhaupt eine
separat implementierte Funktion benutzen muss.  Der Compiler könnte
genausogut das undefinierte Verhalten erkennen und dann irgendwas
tun (beispielsweise auch gar nichts).

von Jobst Q. (joquis)


Lesenswert?

Jörg W. schrieb:
> Schlimmer noch: keiner sagt, dass der Compiler überhaupt eine
> separat implementierte Funktion benutzen muss.  Der Compiler könnte
> genausogut das undefinierte Verhalten erkennen und dann irgendwas
> tun (beispielsweise auch gar nichts).

Woran sollte er das erkennen? In der string.h, aus der er seine 
Informationen über strcpy bezieht, steht nichts davon.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jobst Q. schrieb:
> Woran sollte er das erkennen?

Am Namen der Funktion, das genügt.

Im hosted environment sind die Namen der Standardbibliothek
ausschließlich für die im Standard beschriebene Funktionalität
zu benutzen (reserved identifier), eben damit der Compiler dort
Optimierungspotenzial hat.  Auf diese Weise darf er beispielsweise
1
sizeof("foo")

durch die Konstante 4 bereits zur Compilezeit ersetzen.

von Rolf M. (rmagnus)


Lesenswert?

Jobst Q. schrieb:
> Jörg W. schrieb:
>> Schlimmer noch: keiner sagt, dass der Compiler überhaupt eine
>> separat implementierte Funktion benutzen muss.  Der Compiler könnte
>> genausogut das undefinierte Verhalten erkennen und dann irgendwas
>> tun (beispielsweise auch gar nichts).
>
> Woran sollte er das erkennen? In der string.h, aus der er seine
> Informationen über strcpy bezieht, steht nichts davon.

Hat Jörg doch geschrieben: Moderne Compiler nutzen Funktionen wie strcpy 
nicht unbedingt als klassische Bibliotheksfunktion, sondern 
implementieren sie selber, direkt im Compiler.
Hier mal eine Liste der Funktionen, die GCC bereits eingebaut hat:
https://gcc.gnu.org/onlinedocs/gcc-6.3.0/gcc/Other-Builtins.html#Other-Builtins
In dieser recht großen Liste ist auch strcpy enthalten.

von Rolf M. (rmagnus)


Lesenswert?

Jörg W. schrieb:
> Auf diese Weise darf er beispielsweise
> sizeof("foo")
> durch die Konstante 4 bereits zur Compilezeit ersetzen.

Schlechtes Beispiel, da sizeof ein Operator ist und keine 
Bibliotheks-Funktion. Besser:
Er kann
1
strlen("foo");
zur Compliezeit durch 3 ersetzen, statt zur Laufzeit die Funktion 
aufzurufen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Besser:

Ja, sorry, genau das war meine Intention. ;-)

Klar, sizeof kann er immer ersetzen, strlen() nur im hosted mode.

Rolf M. schrieb:
> Moderne Compiler nutzen Funktionen wie strcpy nicht unbedingt als
> klassische Bibliotheksfunktion, sondern implementieren sie selber,
> direkt im Compiler.

Genau daher finde ich es gar nicht so völlig abwegig, dass der
Compiler die Funktion bei erkanntem undefiniertem Verhalten eben
auch komplett wegwirft.  Für solch eine Optimierung muss er
zwangsläufig in gewisser Weise die Argumente analysieren, und bevor
man dann in einen internal compiler error rennt bei undefiniertem
Verhalten, sollte man es halt besser erkennen und reagieren.

Fair wäre es natürlich, dann auch 'ne Warnung zu werfen ;), aber
zu einer Warnung ist er nicht verpflichtet.

: Bearbeitet durch Moderator
von Jobst Q. (joquis)


Lesenswert?

guest schrieb:
> Für jedes Zeichen, daß kein Buchstabe ist immer den kompletten String um
> ein Zeichen zu verschieben ist ziemlich ungünstig. Überleg mal was
> passiert, wenn am Anfang erstmal 50 Ziffern kommen :(.

Die Verschieberei ist sowieso ineffizient. Besser ist es gleich nur die 
erwünschten Zeichen zu kopieren. Durch Rückgabe des Stringendes erspart 
man sich das strlen.
1
char *stpcpy_alpha(char *t,char *s){
2
3
while(*s){
4
  if(isalpha(*s)*t++=*s;
5
  s++;
6
  }
7
*t=0;
8
return t; //Zeiger auf Stringende ('\0')
9
}
10
11
int main(){
12
char *str;
13
char *str_end;
14
char *p;
15
char buf[200];
16
17
    
18
fgets(buf, 200, stdin);
19
str=malloc(200);
20
    
21
str_end=stpcpy_alpha(str,buf);
22
23
p = realloc( str, str_end - str + 1 );
24
if( p != NULL )str = p;
25
26
printf( "%s", str );
27
28
free( str );
29
}

: Bearbeitet durch User
von Jobst Q. (joquis)


Lesenswert?

Rolf M. schrieb:
> Hat Jörg doch geschrieben: Moderne Compiler nutzen Funktionen wie strcpy
> nicht unbedingt als klassische Bibliotheksfunktion, sondern
> implementieren sie selber, direkt im Compiler.

Ein Grund mehr, sie zu vermeiden und lieber selbstgeschriebene 
Funktionen zu verwenden.

von Dumdi D. (dumdidum)


Lesenswert?

Noch was: m.M.n. muss man bei fgets auf Fehler prüfen (Rückgabewert mit 
NULL vergleichen), da im Fehlerfall m.M.n. nicht garantiert ist, dass 
'buf' mit einer '\0' ended. Das wäre dann mit jedem strcpy (strlen) 
fatal. Sollte dieser Bug irgendwann einmal auftreten, würde man ihn auch 
vermutlich nie finden, da vermutlich nicht reproduzierbar.

von Rolf M. (rmagnus)


Lesenswert?

Jobst Q. schrieb:
> Rolf M. schrieb:
>> Hat Jörg doch geschrieben: Moderne Compiler nutzen Funktionen wie strcpy
>> nicht unbedingt als klassische Bibliotheksfunktion, sondern
>> implementieren sie selber, direkt im Compiler.
>
> Ein Grund mehr, sie zu vermeiden und lieber selbstgeschriebene
> Funktionen zu verwenden.

Genau das Gegenteil ist der Fall. Nutzt du selbstgeschriebene 
Funktionen, nimmst du dem Compiler Möglichkeiten der Optimierung.
Aber bitte: Wenn du meinst, es selber besser zu können als ein Team von 
Compiler-Entwicklern, dann mach dir halt die zusätzliche Arbeit.

Dumdi D. schrieb:
> Noch was: m.M.n. muss man bei fgets auf Fehler prüfen (Rückgabewert mit
> NULL vergleichen), da im Fehlerfall m.M.n. nicht garantiert ist, dass
> 'buf' mit einer '\0' ended.

Nicht nur im Fehlerfall, sondern auch bei einem EOF.

von Jobst Q. (joquis)


Lesenswert?

Rolf M. schrieb:
>> Ein Grund mehr, sie zu vermeiden und lieber selbstgeschriebene
>> Funktionen zu verwenden.
>
> Genau das Gegenteil ist der Fall. Nutzt du selbstgeschriebene
> Funktionen, nimmst du dem Compiler Möglichkeiten der Optimierung.
> Aber bitte: Wenn du meinst, es selber besser zu können als ein Team von
> Compiler-Entwicklern, dann mach dir halt die zusätzliche Arbeit.

Meine Funktionen kann der Compiler optimieren wie alles andere im 
Quelltext.
Er kann sie nur nicht durch irgendetwas anderes ersetzen, bloss weil er 
den Namen kennt.

Die Arbeit, eine Funktion zu schreiben, mach ich mir nur einmal und kann 
sie tausendfach einsetzen. Wobei ich genau weiß, was die Funktion macht 
und nicht evt veraltete Dokumentationen wälzen muss.

Sich blind auf den Namen und vielleicht noch eine Ein-Satz-Beschreibung 
zu verlassen, kann manchmal ganz schön daneben gehen.

Wem ist denn zB bewusst, dass strncpy eben nicht wie strcpy immer einen 
gültigen String liefert, sondern im Begrenzungsfall nur ein Char-Array, 
dem die Null fehlt?

Da schreib ich mir lieber meine eigene Funktion, die nebenbei auch das 
Stringende zurückgibt statt dem eh schon bekannten Stringanfang:
1
char* stpcpylim(char *t, const char *s,const char * lim){
2
3
lim--;
4
while(*s && t<lim) *t++=*s++;
5
 *t=0;
6
 return t;
7
 }

lim ist ein Zeiger auf das erste Byte, dass nicht beschrieben werden 
darf. Damit kann man auch mehrere Strings hintereinander kopieren.
1
#define BUFSIZE 256
2
3
int _ main(int argc,char *argv[]){
4
5
char buf[BUFSIZE];
6
char *t,*lim;
7
8
lim = buf + sizeof(buf);
9
10
t=buf;
11
t=stpcpylim(t,"hello ",lim);
12
if(argc > 1)t=stpcpylim(t, argv[1],lim);
13
t=stpcpylim(t," world!",lim);
14
15
printf("%s \n",buf);
16
17
}

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jobst Q. schrieb:
> Wem ist denn zB bewusst, dass strncpy eben nicht wie strcpy immer einen
> gültigen String liefert, sondern im Begrenzungsfall nur ein Char-Array,
> dem die Null fehlt?

Jedem, der die Dokumentation dazu liest.

Wer sie nicht liest, ist natürlich selbst schuld, dem ist aber auch
sonst nicht zu helfen.

Der Vorteil deiner selbst geschriebenen Funktionen: du machst dann
deine eigenen Bugs rein …

von Jobst Q. (joquis)


Lesenswert?

Jörg W. schrieb:
> Der Vorteil deiner selbst geschriebenen Funktionen: du machst dann
> deine eigenen Bugs rein …

Diese Funktionen sind durchaus noch überschaubar. Eventuelle Bugs wären 
auch schnell zu finden.

Das Problem bei strncpy ist ja gerade, dass es kein Bug ist. Ein Bug 
wäre ja wohl korrigiert worden. Es ist ein Fehldesign, dass man bei 
jedem Einsatz durch Code drumherum ausbügeln muss.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jobst Q. schrieb:
> Es ist ein Fehldesign, dass man bei jedem Einsatz durch Code drumherum
> ausbügeln muss.

Kannst du aber ganz einfach ausbügeln, indem du die Anzahl eins
kleiner als den Puffer übergibst und ans Ende des Puffers manuell
eine 0 setzt.  Das genügt ja einmalig (bei globalen oder statischen
Variablen ohnehin automatisch), da diese dann nie wieder überschrieben
wird.

Nerviger finde ich, dass es den kompletten Rest des Puffers immer
mit Nullen füllt.  Wozu sollte das denn gut sein?

Wenn du partout keine der Compiler-Optimierungen bezüglich der
Bibliothek haben willst, kannst du ja auch im freestanding mode
compilieren.  Ich jedenfalls finde die Optimierungen sinnvoll
und benutze daher den hosted mode selbst auf Controllern (für die
ja eigentlich freestanding konzipiert ist).

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.