String-Array an Funktion übergeben

#1
Hallo Forum,
ich bin hier seit einigen Wochen ein bißchen am verzweifeln: Für ein Informatik-Projekt müssen wir String-Arrays in C auffüllen und vor allem wieder auslesen. In einem Buch habe ich eine Lösung gefunden, die zunächst zu funktionieren schien. Leider versagt sie, sobald ich die Routine in eine Funktion verschiebe.

Hier mein Testprogramm, das das Problem isoliert:
C:
/*  Programm String-Test2
    Dieses Programm soll String-Arrays anlegen und wieder auslesen.
*/

#include <stdio.h>
#include <stdlib.h>
#define AnzFarben 4
#define AnzWerte 8


void Ausgabefunktion (char *Stringarray[]){
    char **pointer2; //Pointer auf Pointer auf Char
        for (pointer2=Stringarray; *Stringarray; pointer2=pointer2+1){
        printf("%s\n",*pointer2);
    }//end for pointer
}//endfunc Ausgabefunktion


int main() {

    //Array von Zeigern auf Strings:
    char *vFarben [AnzFarben+1] =     {
                            "Karo",
                            "Herz",
                            "Pik",
                            "Kreuz",
                            0           //Die Null am Ende ist wichtig!
    };

    //Array von Zeigern auf Strings:
    char *vWerte [AnzWerte+1] = {
                        "7",
                        "8",
                        "9",
                        "10",
                        "Bube",
                        "Dame",
                        "Koenig",
                        "As",
                        0               //Die Null am Ende ist wichtig!
    };

    //    Direkte Ausgabe:
    // ------------------------
    printf ("Die beiden Stringarrays werden direkt ausgegeben:\n\n");
    char **pointer; //Pointer auf Pointer auf Char
    for (pointer=vFarben; *pointer; pointer=pointer+1){
        printf("%s\n",*pointer);
    }//end for pointer

    printf ("\n");

    for (pointer=vWerte; *pointer; pointer=pointer+1){
        printf("%s\n",*pointer);
    }//end for pointer

    //  Ausgabe per Funktion:
    // -----------------------
    printf ("\n\nJetzt werden die beiden Stringarrays per Hilfsfunktion ausgegeben:\n");
    Ausgabefunktion(&vFarben[0]);
    Ausgabefunktion(&vWerte[0]);

    return 0;
}//endfunc main
Lasse ich das Programm laufen, erscheint das hier auf der Konsole:

Die beiden Stringarrays werden direkt ausgegeben:

Karo
Herz
Pik
Kreuz

7
8
9
10
Bube
Dame
Koenig
As


Jetzt werden die beiden Stringarrays per Hilfsfunktion ausgegeben:
Karo
Herz
Pik
Kreuz
Segmentation fault

Process returned 139 (0x8B) execution time : 0.003 s
Press ENTER to continue.
Ich muss gestehen, dass ich die Sache mit den Doppel-Pointern auch nicht ganz verstehe - und vor allem die For-Schleife. Hab den Ansatz wie gesagt aus einem Buch. Wäre wirklich sehr dankbar für Hilfe!
 

German

Well-Known Member
c-b Experte
#3
Deine Arrays sind Arrays von Pointers auf char. Die 0 als letztes Element sollte also eigentlich ein NULL sein. Das ändert zwar nichts am Verhalten, ist aber verständlicher.
Der Wert von Pointers ist eine Speicheradresse. In deinen Schleifen bekommen die Variablen pointer bzw. pointer2 immer die Adresse des nächsten Arrayelements. Durch Dereferenzierung greifst du darauf zu. Am Ende kommst du an den Nullpointer. In C ist alles was den Wert 0 hat (0, NULL, '\0') FALSCH und alles was einen Wert ungleich 0 hat ist WAHR. In den Schleifenbedingungen wird genau das abgefragt. Wie @BAGZZlash schon gesagt hat, dereferenzierst du in deiner Funktion an dieser Stelle aber Stringarray und das ist ein Pointer auf das erste Element im Array und ändert sich nicht. Du inkrementierst ja eine andere Variable ;)
 
#4
Das hilft mir immerhin schon ein bißchen. Um jetzt also mal die Schleifenbedingung auseinanderzunehmen: Da steht also sinngemäß folgendes(?):

for ( [Adresse auf Element im Pointer Array]; [bis Inhalt des Array-Elements logisch 0]; [Springe zum nächsten Arrayelement]){
...
}
Das wäre plausibel...die Sache mit der Null hatte ich nämlich schon nicht verstanden.

Warum das Stringarray jetzt aber falsch ist, verstehe ich immernoch nicht. Die Adresse des Pointer-Arrays ist doch richtig. Die soll doch blockweise erhöht werden, um die einzelnen Arrayelemente (ebenfalls Pointer) auszulesen.

Wenn ich Stringarray als Doppelpointer initialisiere ("char **Stringarray[]" in der Funktionsdeklaration) ändert sich nichts.
Im funktionierenden Teil steht ja auch einfach "pointer=vFarben", also Pointer bekommt die Start-Adresse des Arrays.

Was ist Dereferenzierung?

Edit:

OK, ich hab's. Nachdem ich Germans letzten beiden Zeilen mehrfach gelesen habe, habe ich die Abbruchbedingung auf den Inhalt von pointer2 geändert. Jetzt scheint es zu funktionieren:

C:
/*  Programm String-Test2
    Dieses Programm soll String-Arrays anlegen und wieder auslesen.

*/

#include <stdio.h>
#include <stdlib.h>
#define AnzFarben 4
#define AnzWerte 8


void Ausgabefunktion (char *Stringarray[]){
    char **pointer2; //Pointer auf Pointer auf Char
        for (pointer2=Stringarray; *pointer2!=0; pointer2=pointer2+1){
        printf("%s\n",*pointer2);
    }//end for pointer
}//endfunc Ausgabefunktion


int main() {

    //Array von Zeigern auf Strings:
    char *vFarben [AnzFarben+1] =     {
                            "Karo",
                            "Herz",
                            "Pik",
                            "Kreuz",
                            0           //Die Null am Ende ist wichtig!
    };

    //Array von Zeigern auf Strings:
    char *vWerte [AnzWerte+1] = {
                        "7",
                        "8",
                        "9",
                        "10",
                        "Bube",
                        "Dame",
                        "Koenig",
                        "As",
                        0               //Die Null am Ende ist wichtig!
    };

    //    Direkte Ausgabe:
    // ------------------------
    printf ("Die beiden Stringarrays werden direkt ausgegeben:\n\n");
    char **pointer; //Pointer auf Pointer auf Char
    for (pointer=vFarben; *pointer; pointer=pointer+1){
        printf("%s\n",*pointer);
    }//end for pointer

    printf ("\n");

    for (pointer=vWerte; *pointer; pointer=pointer+1){
        printf("%s\n",*pointer);
    }//end for pointer

    //  Ausgabe per Funktion:
    // -----------------------
    printf ("\n\nJetzt werden die beiden Stringarrays per Hilfsfunktion ausgegeben:\n");
    Ausgabefunktion(vFarben);
    Ausgabefunktion(vWerte);

    return 0;
}//endfunc main
Ausgabe auf der Konsole:

Die beiden Stringarrays werden direkt ausgegeben:

Karo
Herz
Pik
Kreuz

7
8
9
10
Bube
Dame
Koenig
As


Jetzt werden die beiden Stringarrays per Hilfsfunktion ausgegeben:
Karo
Herz
Pik
Kreuz
7
8
9
10
Bube
Dame
Koenig
As

Process returned 0 (0x0) execution time : 0.003 s
Press ENTER to continue.
 
Zuletzt bearbeitet:

German

Well-Known Member
c-b Experte
#5
Warum das Stringarray jetzt aber falsch ist, verstehe ich immernoch nicht.
Nein, die Variable Stringarray für die Prüfung in der Schleife zu verwenden ist falsch. Wäre so als würdest du in der main() die Variable vFarben oder vWerte an dieser Stelle verwenden. Tust du aber nicht und deshalb funktioniert es.

Das was das * vor dem Variablenname macht, wenn du darauf zugreifst (nicht bei der Deklaration, wo es bedeutet dass du eine Pointervariable deklarierst).
Beispiel:
int a = 5; Du hast eine Variable a in dessen Speicherbereich der Wert 5 steht.
int *p = &a; Du hast eine Variable p vom Typ Pointer auf int, die du mit der Speicheradresse von a initialisierst.
int b = *p; Du hast eine Variable b in dessen Speicherbereich du den Wert von a kopierst, also die 5. Warum? p enthält die Speicheradresse von a. Mit *p dereferenzierst du den Pointer und greifst auf den Speicherbereich zu, auf den p zeigt.
 
#6
Vielen Dank! Das hatte mich wie gesagt wochenlang geplagt. Jetzt habe ich noch eine Frage zum Fehlerverhalten von C:
Der nächste Segmentation-Fault kommt bestimmt. Wie ist der im letzten Fall zu deuten gewesen?

Wegen der falschen Abbruchbedingung, hat das Programm die ersten vier Strings korrekt ausgegeben. Dann kam der Segmentation-Fault.
Im Grunde genommen hätte das Programm ja auf ewig weitermachen müssen, weil niemals ein Pointer mit logisch NULL gekommen wäre. Das ist aber nicht passiert. Stattdessen gab's 'nen Absturz.
 

German

Well-Known Member
c-b Experte
#7
Du hast den Pointer immer weiter inkrementiert, da es quasi kein Abbruchkriterium gegeben hat. Heißt:
1) Du hast beim letzten Arrayelement in deinem printf Aufruf versucht einen NULL Pointer auszugeben. Was das bewirkt, ist undefiniert. Womöglich startest du eine nordkoreanische Atomrakete ;)
2) Wenn du ohne erkennbaren Fehler darüber hinweg gekommen bist, greifst du nun auf Speicher zu, der nicht mehr zum Array gehört. Also gibt's einen Segfault.
 
#8
...kommt dieser Segfault vom printf oder von der Variablen-Inkrementierung der for-Schleife? Die Inkrementierung ist ja blockweise.
Ich frage, weil ich eigentlich erwartet hätte, dass mir printf bei "zufälligem" Speicher einfach Zeichensalat anzeigt.
 

German

Well-Known Member
c-b Experte
#9
...kommt dieser Segfault vom printf oder von der Variablen-Inkrementierung der for-Schleife?
Kann ich dir nicht beantworten. Wenn die Implementierung der printf Funktion bei dir eine Überprüfung auf einen NULL Pointer beinhaltet, dann gibt es keinen Segfault. Anderenfalls wird versucht zeichenweise an einem NULL Pointer zu lesen, was zum Segfault führen würde. Verursacht wird er aber nie allein durch die Inkrementierung eines Pointerwertes. Aber die Dereferenzierung im printf Argument verursacht ihn dann (was nichts mit dem eben erwähnten Verhalten von printf zu tun hat).

weil ich eigentlich erwartet hätte, dass mir printf bei "zufälligem" Speicher einfach Zeichensalat anzeigt.
Ich denke du solltest mal nachlesen was ein Segfault eigentlich ist. Dein Betriebssystem erlaubt dir nicht auf fremden Speicher zuzugreifen.
 
#11
Das Problem hat sich leider doch noch nicht erledigt:

Ich würde gerne auch umgekehrt was in's Stringarray reinschreiben. Wieder bekomme ich nur Segfaults. Ich gebe exakt die gleiche Adresse wie beim Auslesen an. Der Debugger zeigt mir sogar den richtigen Inhalt.
Sobald ich einen Eintrag mit strncopy überschreiben will, crasht das Programm.

Sinngemäß passiert folgendes (zusammengefasst):
C:
#include <stdlib.h>
#include <stdio.h> 
#include <string.h>
char **Hilfspointer;
char *Hilfspointer2;
char Teststring[]="grün";
int Position=0;

Hilfspointer=(vfarben+Position);  //Zeigerauswahl im Array aus Zeigern
Hilfspointer2=*Hilfspointer;  //Inhalt von Hilfspointer ist der String
strncpy(Hilfspointer2,Teststring,3); // <-- Segfault!
An der Stringlänge kann's nicht liegen, die habe ich vorsichtshalber vorerst auf 3 gesetzt, damit beim Kopieren der Zielstring auch ganz sicher länger ist als der Quell-String.
Mir fiel auf, dass die Position stets mit 8 multipliziert wird, wenn ich sie erhöhe. Der Debugger zeigt mir also bei Position=3 an:
Hilfspointer: <vfarben+24>
...wenn ich einen Breakpoint for dem Segfault setze.

Hilfspointer2 ist der String, der überschrieben werden soll, im Fall von Position=3 also "Kreuz".

Ideen?
 
Zuletzt bearbeitet:

German

Well-Known Member
c-b Experte
#12
Ein Pointer hält die Adresse eines Speicherbereichs. Einen Pointer zu deklarieren, der nicht mal initialisiert ist, enthält also irgendwelche Daten die als Speicheradresse interpretiert werden. Greifst du darauf zu -> Segfault.
Ein Pointer ist also noch lange kein Speicherbereich in den man wahllos reinkopieren kann. Er muss auf einen gültigen Speicherbereich zeigen. Du musst also schon einen Puffer anlegen. Entweder durch die Deklaration eines Pufferarrays ausreichender Größe (der Name des Array ist dann gleichzeitig der Pointer auf das erste Element) oder durch Speicherallokation mittels malloc()/calloc()/realloc(). Letzteres erfordert die Freigabe mit free() wenn der Speicherbereich nicht mehr benötigt wird.

Übrigens - strncpy kopiert die angegebene Anzahl von Zeichen. Es wird aber nicht automatisch die erforderliche Nullterminierung gesetzt. Du solltest dir vor Augen führen was ein String in C ist und wie er funktioniert.
https://www.coding-board.de/resources/c-strings-aufbau-missverstaendnisse-und-pitfalls.71/

Und dann unbedingt mal in Funktionsreferenzen schauen, bevor du irgendwas nutzt. Ich frag mich gerade woher vfarben kommt und für was du einen char** brauchst.
 
#13
Guten Morgen und danke erstmal für die schnelle Antwort.
Wo glaubst Du hätte ich einen Pointer verwendet, der nicht initialisiert gewesen sein soll?

Ich weiß, dass Strings mit einer Null enden sollen und strncpy das nicht unbedingt macht, habe mir die Beschreibungen dazu mehrfach durchgelesen. Das ist jetzt aber auch gar nicht das Problem.

vfarben ist im Code-Teil definiert, den ich anfangs gepostet habe, zu Beginn des Threads. Der neue Schnipsel soll eine Erweiterung dessen sein.
 

German

Well-Known Member
c-b Experte
#14
Wo glaubst Du hätte ich einen Pointer verwendet, der nicht initialisiert gewesen sein soll?
vfarben ist im Code-Teil definiert, den ich anfangs gepostet habe
Ah, du meinst vFarben. Das ist aber eine andere Variable.

Das ist jetzt aber auch gar nicht das Problem.
Dadurch dass du nur 3 Zeichen kopierst wo bereits 4 Zeichen + '\0' stehen, OK.
Ungetestet:
C:
#include <stdio.h>
#include <string.h>

#define AnzFarben 4

void Ausgabefunktion(char **Stringarray, size_t Laenge)
{
  for (char **endptr = Stringarray + Laenge; Stringarray < endptr; puts(*Stringarray++));
}

int main(void)
{
    char *vFarben [] =     {
                            "Karo",
                            "Herz",
                            "Pik",
                            "Kreuz"
    };

    Ausgabefunktion(vFarben, AnzFarben);

    char Teststring[] = "grün";
    char *Hilfspointer = vFarben[2]; // = *(vFarben + 2);

    strncpy(Hilfspointer, Teststring, 3);

    Ausgabefunktion(vFarben, AnzFarben);

    return 0;
}
 
#15
mmmm...die Ausgabefunktion ist noch ein bißchen zu hoch für mich, bin noch C-Anfänger. Ich glaube aber der spannende Teil ist ohnehin weiter unten:
Code:
 char *Hilfspointer = vFarben[2]; // = *(vFarben + 2);
Muss die Zuweisung wirklich direkt bei der Deklaration sein? Im Endgültigen Programm soll die Zuweisung in eine Schleife. Jedenfalls habe ich gerade mal ausprobiert, bei mir die Zuweisung durch solch eine Deklaration zu ersetzen: Leider ohne Erfolg. "Segmentation Fault".

Kann es vielleicht an der Art des Arrays vFarben liegen? Es gab auch Leute im Kurs, die haben das direkt zweidimensional definiert, ungefähr so:
C:
char farb_bezeichner[MAXCOLORS][STRINGSIZE] = {
            {"Caro"},
            {"Herz"},
            {"Pik"},
            {"Kreuz"},
            };
Ich hatte das auch erst auf diese Art, bekam die Strings aber einfach nicht ausgelesen. Wenn es keinen Programmabsturz gab, kamen auf dem Bildschirm abgehackte Strings mit Zeichensalat dazwischen. Deshalb war ich so froh, dass die Sache mit dem Array aus Pointern dann funktionierte.
 

German

Well-Known Member
c-b Experte
#16
die Ausgabefunktion ist noch ein bißchen zu hoch für mich
Joa, das wird noch ...
Muss die Zuweisung wirklich direkt bei der Deklaration sein?
Nein, aber du solltest immer einen Wert initial zuweisen um einen definierten Ausgangszustand zu haben. Erleichtert die Fehlersuche und verhindert undefiniertes Verhalten. Bei Pointervariablen oft NULL.
Whoops. Hab's nicht getestet und sehe momentan meinen Fehler nicht. Muss heute Abend mal probieren. Oder lassen sich Stringliterale nicht überschreiben? Wäre aber auch irgendwie strange, weil ist nicht const deklariert. Muss ich direkt selber nachlesen...
die haben das direkt zweidimensional definiert
Kannst du machen. Dann aber trotzdem ohne geschweifte Klammern um die Strings. Statt individuell Stringlänge + Nullterminierung sind deine Elemente dann immer STRINGSIZE breit.
 

German

Well-Known Member
c-b Experte
#17
Ja, ist tatsächlich nicht veränderbar. Einfach mal versucht den ersten Buchstabe von K auf L zu ändern:
1546449733948.png

Du wirst wohl oder übel ein zweidimensionales Pufferarray anlegen müssen und anschließend Inhalt reinkopieren.
 
#18
Jetzt habe ich direkt mal "Stringliterale" gegoogelt und hier eine Beschreibung für Java gefunden:

Ein Stringliteral ist ein Objekt (der Klasse String). Der Compiler behält die Literale in Ihrem Programm im Auge und wird, wenn er kann, dasselbe Objekt wieder verwenden. (Der Compiler wird nicht versuchen andere Typen von Objekten wieder zu verwenden. Stringliterale sind etwas Besonderes.)
und
Nur ein Objekt wird erzeugt, das die Zeichen "ein Stringliteral" enthält. Das ist sicher, da Strings unveränderbar sind (also wird ein Programm weder versuchen str1 zu ändern noch str2, da Strings nicht geändert werden können). Es gibt keinen Bedarf für zwei Stringobjekte, deren Inhalt identisch ist.
Jetzt ist C zwar nicht Java, die Informationen könnten aber übertragbar sein. Wenn Strings in C auch nicht veränderbar sind, dann kann ich doch auch ein Pufferarray nicht nachträglich verändern. Das heißt, ein Pufferarray könnte genau einmal puffern. Das ist nicht so gut für eine Schleife...

Edit:

Andere Idee: Was passiert denn wenn ich das gesamte Array einfach neu deklariere? Wird es dann überschrieben? Also gleicher Name, gleiche Größe, aber neue Strings.
 

German

Well-Known Member
c-b Experte
#19
Nein C und Java kannst du nicht vergleichen. C hat nicht mal einen Stringtyp geschweige so was wie Klassen. Neudeklaration wird dir einen Fehler geben. Wie gesagt Pufferarray deklarklarieren...
 
#20
Das habe ich mir wohl zu einfach vorgestellt. Ich bekomme das Pufferarray gar nicht in das endgültige Array übertragen. Wollt die Zeiger "umbiegen".
Code:
static char *vFarben[ANZFARBEN+1]={
                                        Pufferarray_Farben[0][0],
                                        Pufferarray_Farben[1][0],
                                        Pufferarray_Farben[2][0],
                                        Pufferarray_Farben[3][0],
                                        0,
                                           };
error: initializer element is not constant
Das Pufferarray wollte ich per strncpy zusammenkopieren, ungefähr so:

Code:
strncpy(&Pufferarray_Farben[AnzahlWoerter-1][0],WortAusDatei,STRLAENGE_F);
Ich weiß gar nicht, ob das so überhaupt geht. Ohnehin sind jetzt plötzlich ganz viele Sachen unklar:
-Wie muss das Pufferarray initialisiert werden, damit ich da Strings reinkopieren kann?
-Wenn ich der Reihe nach vier Pointer auf einen sich ständig ändernden String umlenke (WortAusDatei hat bei jedem Schleifendurchlauf einen anderen Inhalt), bekomme ich dann vier gleiche String-Startadressen oder vier verschiedene?
-Wenn es vier gleiche Stringadressen sind: Wie bekäme ich X verschiedene?
 
Oben