Forum: PC-Programmierung C - Pointer Arithmethik auf Strukturen?


von MOP (Gast)


Lesenswert?

Hallo zusammen,

ich habe eine Frage bezüglich Pointer Arithmethik auf Strukturen.

Beispiel:
1
const char *beispiel = "Hallo Welt";
2
do {
3
   printf("%c", *beispiel);
4
} while(*beispiel++);

Ganz konkret habe ich eine Datei in der Highscores abgespeichert werden.
Jeder Eintrag wird als Structure in der Datei in Binär Form gespeichert.

Ich habe dann eine Funktion die die Datei ausliest und mir die 
entsprechenden Einträge als Pointer zurückgibt.
1
typedef struct {
2
   char owner[50];
3
   int score;
4
} HIGHSCORE;
5
6
...
7
8
HIGHSCORE *ReadHighScore()
9
{
10
    FILE *highscore_file = fopen("Bilder/Einstellungen/HighscoresDATA.MOP", "rb");
11
    long file_size;
12
13
    if(!highscore_file)
14
    {
15
        printf("File Subsystem Fehler: Highscores Datei kann nicht geöffnet werden.\n");
16
        return NULL;
17
    }
18
    else
19
    {
20
        // Datei Größe in Bytes ermitteln
21
        fseek(highscore_file, 0L, SEEK_END);
22
        file_size = ftell(highscore_file);
23
        rewind(highscore_file);
24
        entrys = file_size / sizeof(HIGHSCORE);
25
        
26
        // Daten auslesen
27
        if(entrys == 0)
28
            return NULL;
29
        else
30
        {
31
            HIGHSCORE entry[entrys];
32
            int i = 0;
33
            while(fread(&entry[i], sizeof(HIGHSCORE), 1, highscore_file))
34
            {
35
                //printf("Test %s - %d\n", entry[i].owner, entry[i].score);
36
                i++;
37
            }
38
            return entry;
39
        }
40
    }
41
    fclose(highscore_file);
42
    highscore_file = NULL;
43
}
44
45
...
46
47
HIGHSCORE *pointer = ReadHighScores();
48
49
do {
50
   printf("Name: %s - Punkte: %d\n", pointer->name, pointer->score);
51
} while( *pointer++ );
52
53
//printf("Name: %s - Punkte: %d\n", pointer->name, pointer->score);

Wenn ich das genau mache, wie beim String, spuckt der Compiler natürlich 
den Fehler raus, dass das nur bei Skalaren Variablen geht.

Die Datei hat momentan zwei Einträge.

Eine komische Sache ist mir auch noch aufgefallen.

Und zwar wenn ich folgendes mache:
Beispiel 2:
1
....
2
HIGHSCORE *ReadHighScore()
3
{
4
....
5
printf("Test1 %s - %d\n", entry[i].owner, entry[i].score); // Nicht auskommentiert diese Zeile
6
....
7
}
8
9
HIGHSCORE *pointer = ReadHighScore();
10
11
printf("Test2 %s - %d\n", pointer->owner, pointer->score);
12
....

Bekomme ich folgende Ausgabe auf der Console
1
Test1 Master of Penetrator - 111111
2
Test1 Master of Destructor - 222222
3
Test2 Master of Penetrator - 111111

Das sieht erstmal prima aus.



Jedoch wenn ich in Beispiel2 in der Funktion die "Test1" Ausgabe 
auskommentiere
1
//printf("Test1 %s - %d\n", entry[i].owner, entry[i].score);

Bekomme ich folgende Seltsame Ausgabe:
1
Test2 ɲa - 111111

Wieso kriegt er den String nicht mehr korrekt dargestellt, es ist ja 
nichts anders..

Meine Fragen nun dazu:
1. Wie kann ich die Dateneinträge ausgeben lassen, ohne mir eine 
Hilfsfunktion zu schreiben, um die Zahl der Einträge zu bestimmen?
2. Wie kommt das Verhalten in Beispiel 2 zustande ?
   Ich habe ja lediglich als Unterschied eine Ausgabe wegkommentiert.

Ich würde mich über Input freuen

Vielen Dank!

von Felix U. (ubfx)


Lesenswert?

Du gibst einen Pointer auf ein lokal alloziertes Array zurück. Der ist 
nach Ende der Unterfunktion nicht mehr gültig, und dass es überhaupt 
funktioniert ist reines Glück.

Du solltest dir erst einmal die Grundlagen von C aneignen bevor du 
versuchst, komplexere Programme zu schreiben.

von (prx) A. K. (prx)


Lesenswert?

Die Adresse einer lokalen (auto) Variablen als Return-Wert zu verwenden, 
ist nicht zulässig.

von Theor (Gast)


Lesenswert?

Ich habe nicht den ganzen Code im Detail angeschaut, allerdings fällt 
mir folgendes auf:
1
...
2
        else
3
        {
4
            HIGHSCORE entry[entrys];
5
...

In Begriffen der Sprachbeschreibung von C definierst Du da innerhalb 
eines Blockes eine Variable. "Definieren" heisst in diesem 
Zusammenhang, dass Speicher auf dem Heap für diese Variable angelegt 
wird. "Block" heisst in diesem Zusammenhang, eine Folge von Anweisungen 
im Gegensatz zu einer einzelnen Anweisung. "Block" heisst aber in Bezug 
auf Definitionen von Variablen auch, dass diese Variable auch nur 
innerhalb dieses Blockes zugreifbar ist UND das diese Variable aufhört 
zu existieren, sobald der Block verlassen wird.

Das hat zur Folge, dass jede Anweisung, wie Dein printf, die 
ausserhalb des fraglichen Blocks ausgeführt wird, NICHT mehr auf diese 
Daten zugreifen kann. Fraglos hast Du irgendeine andere Definition mit 
gleichem Variablennamen - sonst würde das Programm nicht ohne Fehler 
kompiliert werden -, aber diese zweite Definition benennt einen 
gesonderten, anderen Speicherbereich, der mit dem innerhalb des 
Else-Blockes nichts zu tun hat.

Daher wird das printf auch nichts ausgeben, was zuvor mit dem Code aus 
der Datei eingelesen wurde.

Falls Du dazu noch Fragen hast, bitte einfach fragen. Klar, dass das 
nicht beim ersten lesen sitzt. Wenn möglich, lies bitte auch über 
Definitionen und Geltungsbereich von Variablen nach.

von Hans (Gast)


Lesenswert?

Du legst in ReadHighScore das Array entry auf dem Stack an und gibst 
einen Zeiger darauf zurück. Sobald die Funktion zurückkehrt, ist das 
Array aber nicht mehr gültig!

Dass es bei Dir manchmal scheinbar funktioniert, ist reines Glück (oder 
Pech ...), wenn die Daten im Speicher noch nicht von einer anderen 
Funktion überschrieben wurden.

Du hast zwei Möglichkeiten:
- Du legst den Speicher außerhalb von ReadHighScore an und übergibst 
einen Zeiger auf den Speicher an ReadHighScore.
- Du legst den Speicher in ReadHighScore mit malloc() an und gibst den 
Zeiger darauf zurück. Der Aufrufer der Funktion muss ihn dann mit free() 
wieder freigeben!

Die Zahl der Einträge sollte ReadHighScore an den Aufrufer zurückgeben. 
Im Gegensatz zu einem String hat Dein Array ja kein String-Ende-Zeichen, 
mit dem der Aufrufer feststellen kann, dass die Daten zu Ende sind.

von Theor (Gast)


Lesenswert?

Eine kleine Korrektur:

Ich habe hier: 
Beitrag "Re: C - Pointer Arithmethik auf Strukturen?" 
geschrieben, dass die Variablen innerhalb des Blocks auf dem Heap 
angelegt wird.

Das ist, so nicht richtig, und mindestens zwei Antworter haben korrekt 
geschrieben, dass die Variable auf dem Stack angelegt wird. Ein 
Flüchtigkeitsfehler von mir. Sorry.

Den Rest meiner Antwort berührt dieser (mein) Fehler aber nicht.

von MOP (Gast)


Lesenswert?

Hallo zusammen,

vielen Dank erstmal für die Antworten!

Ich wollte eben nicht, dass ich in der Funktion Speicher reserviere aber 
nicht direkt wieder freigeben kann. Sodass man diesen nach Aufruf der 
Funktion irgendwo anders tuen muss, man könnte es ja vergessen ;-)

Das mit dem Gültigkeitsbereich erklärts vielen Dank dafür. Ich werde es 
umbauen.

Gruß

von MOP (Gast)


Lesenswert?

MOP schrieb:
> Hallo zusammen,
>
> vielen Dank erstmal für die Antworten!
>
> Ich wollte eben nicht, dass ich in der Funktion Speicher reserviere aber
> nicht direkt wieder freigeben kann. Sodass man diesen nach Aufruf der
> Funktion irgendwo anders tuen muss, man könnte es ja vergessen ;-)
>
> Das mit dem Gültigkeitsbereich erklärts vielen Dank dafür. Ich werde es
> umbauen.
>
> Gruß

Habe diese umgebaut, falls sich jemand dafür interessiert:
Habe dazu eine übergeordnete Struktur angelegt, in der auch die Anzahl 
der Einträge gespeichert wird.

Funktioniert jetzt auch super.

Vielen Dank.
1
typedef struct {
2
    char owner[50];
3
    int score;
4
} HIGHSCORE;
5
6
typedef struct {
7
    HIGHSCORE *entrys;
8
    size_t count_entrys;
9
} HIGHSCORELISTE;
10
11
...
12
13
14
HIGHSCORELISTE ReadHighScore()
15
{
16
    FILE *highscore_file = fopen("Bilder/Einstellungen/HighscoresDATA.MOP", "rb");
17
18
    HIGHSCORELISTE output = {NULL, 0};
19
    HIGHSCORE *entrys;
20
21
    long file_size;
22
    size_t entry_count;
23
24
    if(!highscore_file)
25
    {
26
        printf("File Subsystem Fehler: Highscores Datei kann nicht geöffnet werden.\n");
27
    }
28
    else
29
    {
30
        // Datei Größe in Bytes ermitteln
31
        fseek(highscore_file, 0L, SEEK_END);
32
        file_size = ftell(highscore_file);
33
        rewind(highscore_file);
34
        entry_count = file_size / sizeof(HIGHSCORE);
35
36
        // Daten allozieren
37
        entrys = malloc(entry_count * sizeof(HIGHSCORE));
38
39
        // Daten auslesen
40
        if(entrys > 0)
41
        {
42
            fread(entrys, sizeof(HIGHSCORE), entry_count, highscore_file);
43
            output.entrys = entrys;
44
            output.count_entrys = entry_count;
45
        }
46
47
    }
48
49
    fclose(highscore_file);
50
    highscore_file = NULL;
51
52
    return output;
53
}
54
55
...
56
HIGHSCORELISTE liste = ReadHighScore();
57
size_t s;
58
for(s=0; s<liste.count_entrys; s++)
59
    printf("Eintrag: %s - %d Punkte\n", liste.entrys[s].owner, liste.entrys[s].score);
60
free(liste.entrys);
61
liste.entrys = NULL;

von (prx) A. K. (prx)


Lesenswert?

Pointer und 0 vergleicht man mit == oder !=, nicht aber mit >.
Und man vergleicht (in C) mit NULL, nicht mit 0.

von Theor (Gast)


Lesenswert?

Gut. Die Lösung ist formal korrekt. Dein Problem gelöst.

Einen Hinweis allgemeiner Natur möchte ich allerdings hinzufügen.

Es spielt bei Deiner Anwendung keine grosse Rolle, aber Du gibst eine 
Struktur zurück, die aus einem Zeiger und einem Integer besteht. Das 
bedeutet, dass die Struktur kopiert wird, denn sie existiert innerhalb 
der Funktion auf dem Stack und muss in den Stackbereich, der aufrufenden 
Funktion kopiert werden. Das verlangsamt das Programm, wenn auch nur 
minimal.

Soweit so gut. Wie gesagt: Die Zeit spielt bei dieser kleinen Struktur 
auch nur eine geringe Rolle, da die Struktur klein ist.

Allerdings vermeiden, - jedenfalls ältere Programmierer -, das aus 
Gewohnheit grundsätzlich, weil der zusätzliche Aufwand dazu gering ist. 
Sie übergeben fast immer Zeiger auf Strukturen, weil das beim Verlassen 
der Funktion die minimal mögliche Datenmenge kopiert.

Du wendest, - ganz richtig -, ein, dass Du den Speicher dann wieder 
freigeben musst. Allerdings musst Du das bei Deiner jetzigen Lösung 
ohnehin tun. Zwar nicht die HIGHSCORELISTE aber die entries. Es wäre 
also nur ein zusätzlicher Aufruf von malloc und free.

Das nur als Hinweis für weitere Projekte. Ob Du das berücksichtigst oder 
nicht, muss Dir überlassen bleiben.

von MOP (Gast)


Lesenswert?

@A.K.
Vielen Dank für den Hinweis, war eben noch etwas zu schnell beim ändern.
File Pointer wird jetzt mit NULL verglichen.
Und die andere Abfrage sollte natürlich so sein:
if(entrys != NULL && entry_count > 0)

Ich versuchs prinzipiell erstmal zum laufen zu bringen, außer mein 
Compiler(mingw32) warnt mich oder spuckt Fehler heraus. Optimieren kann 
man ja später, sonst wird man ja nie fertig ;-)

@Theor
Mal Offtopic - weil du es gerade geschrieben hast -
entries oder entrys? Bin mir gerade nicht sicher ;-)

In meinen aktuellen Hobbyprojekt habe ich sehr viele Strukturen und 
unterschiedliche Funktionen in unterschiedliche Dateien ausgelagert.
Weil das sonst für mich zu unübersichtlich wird.

Wäre es dann später, wenns irgendwann fertig ist, sinnvoll z.B. eine 
"Globale" Header/Programm Datei anlege, in der ich alle Strukturen 
definiere, und initialisiere. Und mittels Übergabe an den verschiedenen 
Subdateien beschreiben/bearbeiten lasse?

Vielen Dank Gruß!

von Daniel A. (daniel-a)


Lesenswert?

Wenn du einfach so ein solches struct aus dem Speicher in eine Datei 
schreibst/einlest, kann es passieren, dass das Program die Datei auf 
anderen Geräten nicht richtig einlesen kann, wegen endianness, 
Datentypen mit anderen grössen oder padding, etc. Man kann das auf 3 
wegen vermeiden:
1) Nutze das packed attribut, datentypen mit fester breite aus stdint.h 
(z.B. uint32_t), und verwende vor dem speichern/einlesen hton/ntoh
2) Schreibe parser und serializer Funktionen, die die umwandlung der 
Daten in ein Binärformat übernehmen.
3) Kombiniere 1 und 2

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

MOP schrieb:
> Habe dazu eine übergeordnete Struktur angelegt, in der auch die Anzahl
> der Einträge gespeichert wird.
>
> Funktioniert jetzt auch super.

Das funktioniert nur deswegen, weil Du jetzt die Struktur selbst 
zurückgibst und nicht ihre Adresse:
1
// alt
2
HIGHSCORE *ReadHighScore()
3
4
// neu
5
HIGHSCORELISTE ReadHighScore()

Das hättest Du aber auch ohne Deine übergeordnete Struktur hinbekommen 
können.

Daß es nicht ratsam ist, größere Datenstrukturen als 
Funktionsrückgabewerte zu verwenden, sollte Dir klar sein, die Dinger 
landen nämlich jeweils als Kopie vollständig auf dem Stack, und das 
Zuweisen des Ergebnisses ist wiederum auch eine potentiell 
performancefressende Operation.

von Hans (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> Daß es nicht ratsam ist, größere Datenstrukturen als
> Funktionsrückgabewerte zu verwenden, sollte Dir klar sein, die Dinger
> landen nämlich jeweils als Kopie vollständig auf dem Stack, und das
> Zuweisen des Ergebnisses ist wiederum auch eine potentiell
> performancefressende Operation.

Muss nicht sein. Der Compiler kann die Werte auch gleich an ihre 
Zieladresse schreiben (return value optimization).

von (prx) A. K. (prx)


Lesenswert?

Manche ABIs definieren Returnwerte in Registern auch für struct, bis zu 
einer bestimmten Grösse.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Hans schrieb:
> Muss nicht sein.

Gewiss. Aber ohne Not so zu programmieren, daß einen nur noch der 
Optimierer aus dem Sumpf holt, halte ich für ... suboptimal.

von nur mal so (Gast)


Lesenswert?

Warum
  HIGHSCORELISTE output = {NULL, 0};
nicht ausserhalb der Funktion definieren und als call by reference 
Parameter übergeben?

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.