mehrdimensionaler Array aus eigenen Datentypen

Schnelle objektorientierte, kompilierende Programmiersprache.
Antworten
LinkinMaster
Beiträge: 9
Registriert: Mo Jan 27, 2020 4:50 pm

mehrdimensionaler Array aus eigenen Datentypen

Beitrag von LinkinMaster » Fr Jan 31, 2020 2:55 pm

Hallo zusammen,
habe folgendes Problem. Ich versuche gerade Minesweeper in C zu programmieren. Dafür wollte für jedes Feld ein Datentyp generieren der alle nötigen Informationen enthält dieser muss mehrdimensional verfügbar sein und als Pointer an alle benötigten Funktionen übergeben werden Damit drauf zugegriffen werden kann. Das funktionier bei mir Leider nicht!
Ich bekomme Immer einen Speicherzugriffsfehler wenn ich auf ein Feld in dem Array zugreifen möchte. Was mache ich da Falsch?

Vielen Dank im voraus für Hilfe!

Code: Alles auswählen

// Bibliotheken einbindung
#include <stdio.h>
#include <stdlib.h>

// Definition der verwendeten Konstanten
#define MAX_SPIELFELD 26	// Maximale Zeilen und Spaltenzahl
#define MIN_SPIELFELD 10		// Minimale Zeilen und Spaltenzahl

// Zur Darstellung der Zustände eines Feldes
typedef struct {
	int Bombe;		// 1 falls Mine sonst 0
	int Umgebung;		// 1 falls Markierung gesetzt sonst 0
	int Markierung;		// 0->8 Anzahl der Angrenzenden Anzahl Minen
	int Aufgedeckt;		// 0 falls Feld verdeckt sonst 1 bereits aufgedeckt
} sFeld;

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

	int Zeilen=0, Spalten=0;	// Gewüschte Spielfeldgroesse
	int StructGroesse = 0;	// Groesse des struct's fuer das Spielfeld

	sFeld * Spielfeld;

	// Fehlerbehandlung des Komandozeilenparameter Aufrufs
	if(argc != 4){		// Test, dass genau drei Parameter übergeben werden
		printf("\nDas Programm wurde mit falschen Komandozeilenparametern aufgerufen");
		printf("\nKorrekter Aufruf mit : ./POOL3_006 <A> <B> <C>");
		printf("\nA : Dateiname zum speichern des Spieldeldes");
		printf("\nB : Anzahl der m Zeilen des Spielfeldes");
		printf("\nC : Anzahl der n Spalten des Spielfeldes\n");
		return -1;
	}

	else if(atoi(argv[2]) < MIN_SPIELFELD || atoi(argv[2]) > MAX_SPIELFELD || atoi(argv[3]) < MIN_SPIELFELD || atoi(argv[3]) > MAX_SPIELFELD){	// Kontrolle Spielfeldgrösse 10<=n,m<=26
		printf("\nDas Spielfeld muss in folgendem Intervall liegen : %i <= n,m <= %i\n",MIN_SPIELFELD,MAX_SPIELFELD);
		return -1;
	}

	else{
		// Speicherreservierung für unser mehrdimensinales Array der einzenen struct Felder
		Zeilen=atoi(argv[2]);
		Spalten=atoi(argv[3]);
		StructGroesse = Zeilen * Spalten;

		Spielfeld = (sFeld *) calloc(StructGroesse, sizeof(sFeld));

		if(Spielfeld == NULL){		// Fehlermeldung falls der Speicher nicht alloziert werden kann
			printf("\nNicht genügend Speicherplatz!");
			return -1;
		}


		// Das Spielfeld wird erstellt
		Erstelle_Spielfeld(argv, Spielfeld);

		return 0;
	}
}



// Spielfeld erstellen

void Erstelle_Spielfeld(char * argv[], sFeld * Spiel[atoi(argv[2])][atoi(argv[3])]){


	printf("Klappt!\n");
	Spiel[1][1]->Bombe=1;
	Spiel[3][3]->Bombe=1;
/*
	printf("Test: %i %i",Spiel[0][0]->Bombe, Spiel[3][3]->Bombe );

*/

}



nufan
Wiki-Moderator
Beiträge: 2557
Registriert: Sa Jul 05, 2008 3:21 pm

Re: mehrdimensionaler Array aus eigenen Datentypen

Beitrag von nufan » Fr Jan 31, 2020 3:56 pm

Hallo!
LinkinMaster hat geschrieben:
Fr Jan 31, 2020 2:55 pm

Code: Alles auswählen

void Erstelle_Spielfeld(char * argv[], sFeld * Spiel[atoi(argv[2])][atoi(argv[3])]){
Ich bin etwas überrascht, dass das der Compiler überhaupt akzeptiert. Der Typ von "Spiel" passt nicht zu jenem in "main()" und auch bei den "atoi()"-Aufrufen innerhalb der Signatur bin ich skeptisch.

Bei Problemen mit dynamischer Speicherallokierung ist das Programm "valgrind" übrigens sehr hilfreich.

LinkinMaster
Beiträge: 9
Registriert: Mo Jan 27, 2020 4:50 pm

Re: mehrdimensionaler Array aus eigenen Datentypen

Beitrag von LinkinMaster » Fr Jan 31, 2020 10:42 pm

Wie finde ich den das Programm valgrind?

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: mehrdimensionaler Array aus eigenen Datentypen

Beitrag von Xin » Sa Feb 01, 2020 1:42 pm

LinkinMaster hat geschrieben:
Fr Jan 31, 2020 10:42 pm
Wie finde ich den das Programm valgrind?
Valgrind ist in den Repositorys der meisten Linux-Distributionen. Du musst also nur Deinen Packetmanager erklären, dass Du gerne valgrind auf Deinem System haben möchtest.
Unter MacOS ist es soweit ich weiß möglich valgrind zu installieren, aber umständlich.
Windows kann mit valgrind nichts anfangen.

Aber ich glaube, hier gibt es einfachere Antworten. Zum einen muss ich Nufan recht geben.

Code: Alles auswählen

void Erstelle_Spielfeld(char * argv[], sFeld * Spiel[atoi(argv[2])][atoi(argv[3])]){
Ich habe rund 25 Jahre Erfahrung mit C und kann Dir nicht sagen, was der Compiler da draus macht. Er kompiliert es. Keine Ahnung, wieso.
Was da definitiv nicht draus wird, ist, was Du haben willst.
Der Code muss geändert werden!

Ich habe es mal in den Compiler geworfen, weil ich selbst erstaunt bin, dass der Compiler das vergleichsweise kommentarlos mit drei Warnings schluckt.
Mit Debug-Informationen (-g)

Code: Alles auswählen

xin@dualpower:~/proggenOrg/LinkinMaster$ gcc array.c -g
array.c: In function ‘main’:
array.c:55:3: warning: implicit declaration of function ‘Erstelle_Spielfeld’ [-Wimplicit-function-declaration]
   55 |   Erstelle_Spielfeld(argv, Spielfeld);
      |   ^~~~~~~~~~~~~~~~~~
array.c: At top level:
array.c:65:6: warning: conflicting types for ‘Erstelle_Spielfeld’
   65 | void Erstelle_Spielfeld(char * argv[], sFeld * Spiel[atoi(argv[2])][atoi(argv[3])]){
      |      ^~~~~~~~~~~~~~~~~~
array.c:55:3: note: previous implicit declaration of ‘Erstelle_Spielfeld’ was here
   55 |   Erstelle_Spielfeld(argv, Spielfeld);
      |   ^~~~~~~~~~~~~~~~~~
Gucken wir uns kurz etwas Code an:

Code: Alles auswählen

Spielfeld = (sFeld *) calloc(StructGroesse, sizeof(sFeld));
sFeld ist ein (1!) Struct. Nur eins. Du greifst in ErstelleSpielfeld aber auf ein Array zu.

Was sagt valgrind dazu?

Code: Alles auswählen

xin@dualpower:~/proggenOrg/LinkinMaster$ valgrind ./a.out 10 10 10 
==5244== Memcheck, a memory error detector
==5244== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5244== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==5244== Command: ./a.out 10 10 10
==5244== 
Klappt!
==5328== Invalid write of size 4
==5328==    at 0x1093BA: Erstelle_Spielfeld (array.c:69)
==5328==    by 0x109338: main (array.c:55)
==5244==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==5244== 
==5244== 
==5244== Process terminating with default action of signal 11 (SIGSEGV)
==5244==  Access not within mapped region at address 0x0
==5244==    at 0x1093BA: Erstelle_Spielfeld (in /home/xin/proggenOrg/LinkinMaster/a.out)
==5244==    by 0x109338: main (in /home/xin/proggenOrg/LinkinMaster/a.out)
==5244==  If you believe this happened as a result of a stack
==5244==  overflow in your program's main thread (unlikely but
==5244==  possible), you can try to increase the size of the
==5244==  main thread stack using the --main-stacksize= flag.
==5244==  The main thread stack size used in this run was 8388608.
==5244== 
==5244== HEAP SUMMARY:
==5244==     in use at exit: 1,600 bytes in 1 blocks
==5244==   total heap usage: 2 allocs, 1 frees, 2,624 bytes allocated
==5244== 
==5244== LEAK SUMMARY:
==5244==    definitely lost: 0 bytes in 0 blocks
==5244==    indirectly lost: 0 bytes in 0 blocks
==5244==      possibly lost: 0 bytes in 0 blocks
==5244==    still reachable: 1,600 bytes in 1 blocks
==5244==         suppressed: 0 bytes in 0 blocks
==5244== Rerun with --leak-check=full to see details of leaked memory
==5244== 
==5244== For lists of detected and suppressed errors, rerun with: -s
==5244== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Speicherzugriffsfehler (Speicherabzug geschrieben)
"Invalid write of size 4"
Du schreibst irgendwo 4 Byte falsch hin in Zeile 69.
Ob das Programm da knallt steht hier nicht.

Fragen wir gdb

Code: Alles auswählen

xin@dualpower:~/proggenOrg/LinkinMaster$ gdb ./a.out 
GNU gdb (Ubuntu 8.3-0ubuntu1) 8.3
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...
(gdb) run 10 10 10
Starting program: /home/xin/proggenOrg/LinkinMaster/a.out 10 10 10
Klappt!

Program received signal SIGSEGV, Segmentation fault.
Erstelle_Spielfeld (argv=0x7fffffffde88, Spiel=0x5555555592a0) at array.c:69
69              Spiel[1][1]->Bombe=1;
Er fliegt tatsächlich bei Zeile 69 raus, wenn er versucht auf das Array zu schreiben, was ja nur ein einziges Feld ist.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

LinkinMaster
Beiträge: 9
Registriert: Mo Jan 27, 2020 4:50 pm

Re: mehrdimensionaler Array aus eigenen Datentypen

Beitrag von LinkinMaster » Sa Feb 01, 2020 8:37 pm

Wow, danke für deine sehr fundierte und analytische Antwort!
Um mein Problem jetzt zu lösen:
- An welche Stelle, erstelle ich jetzt ein zweidimensionales Array von sFeld?
- Wie alloziere ich dafür korrekt den Speicher?
- Reicht ein einfacher Pointer auf das generierte Array als Parameter der Funktionen aus?
- Wie greife ich in den Funktionen auf die einzelnen Felder zu?

Ich hatte den printf("Klappt!"); extra zum testen drinnen damit ich sehe ob der Funktionsaufruf klappt.
Sobald ich auf das vermeintlich korrekt erstellten Array zugreifen versuche kommt der Speicherzugriffsfehler.
Das habe ich schon verstanden das da der Fehler liegt!

Kann mir vielleicht einer mit den Code helfen wie ich die Syntax korrekt Anwende?

nufan
Wiki-Moderator
Beiträge: 2557
Registriert: Sa Jul 05, 2008 3:21 pm

Re: mehrdimensionaler Array aus eigenen Datentypen

Beitrag von nufan » Di Feb 04, 2020 7:32 pm

Xin hat geschrieben:
Sa Feb 01, 2020 1:42 pm
Gucken wir uns kurz etwas Code an:

Code: Alles auswählen

Spielfeld = (sFeld *) calloc(StructGroesse, sizeof(sFeld));
sFeld ist ein (1!) Struct. Nur eins. Du greifst in ErstelleSpielfeld aber auf ein Array zu.
Ich kann das nicht ganz nachvollziehen. Hier wird über "calloc()" Speicher für "StructGroesse" Elemente vom Typ "sFeld" allokiert. Der Name "StructGroesse" ist etwas unglücklich gewählt, aber prinzipiell sieht der Code nicht falsch aus.
LinkinMaster hat geschrieben:
Sa Feb 01, 2020 8:37 pm
- An welche Stelle, erstelle ich jetzt ein zweidimensionales Array von sFeld?
Falls du den Speicher in "main()" allokieren möchstest, ist das bereits die korrekte Position. Eine weitere Variante wäre die Allokierung in die Funktion "Erstelle_Spielfeld()" zu verschieben.
LinkinMaster hat geschrieben:
Sa Feb 01, 2020 8:37 pm
- Wie alloziere ich dafür korrekt den Speicher?
Für ein eindimensionales Array ist dein Code korrekt. Zweidimensional könnte das ganze so aussehen:

Code: Alles auswählen

sFeld **Spielfeld;
Spielfeld = (sFeld **) calloc(Spalten, sizeof(sFeld *));
for (int i = 0; i < Spalten; i++)
    Spielfeld[i] = (sFeld *) calloc(Zeilen, sizeof(sFeld));
LinkinMaster hat geschrieben:
Sa Feb 01, 2020 8:37 pm
- Reicht ein einfacher Pointer auf das generierte Array als Parameter der Funktionen aus?
Nachdem du die Größe des Spielfelds nicht statisch festlegst, solltest du auch die verwendeten Dimensionen übergeben.
LinkinMaster hat geschrieben:
Sa Feb 01, 2020 8:37 pm
- Wie greife ich in den Funktionen auf die einzelnen Felder zu?
Verwendest du die oben gezeigte zweidimensionale Allokierung, sieht der Zugriff folgendermaßen aus:

Code: Alles auswählen

void Erstelle_Spielfeld(sFeld ** Spiel)
{
    Spiel[1][1].Bombe=1;
    Spiel[3][3].Bombe=1;
}
Möchtest du weiterhin ein eindimensionales Array verwenden, sieht der Zugriff so aus:

Code: Alles auswählen

void Erstelle_Spielfeld(sFeld * Spiel, int Zeilen, int Spalten)
{
    Spiel[Spalten * 1 + 1].Bombe=1;
    Spiel[Spalten * 3 + 3].Bombe=1;
}
Übrigens solltest du den reservierten Speicher sobald du ihn nicht mehr brauchst mit free() wieder freigeben.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: mehrdimensionaler Array aus eigenen Datentypen

Beitrag von Xin » Di Feb 04, 2020 9:17 pm

nufan hat geschrieben:
Di Feb 04, 2020 7:32 pm
Xin hat geschrieben:
Sa Feb 01, 2020 1:42 pm
Gucken wir uns kurz etwas Code an:

Code: Alles auswählen

Spielfeld = (sFeld *) calloc(StructGroesse, sizeof(sFeld));
sFeld ist ein (1!) Struct. Nur eins. Du greifst in ErstelleSpielfeld aber auf ein Array zu.
Ich kann das nicht ganz nachvollziehen. Hier wird über "calloc()" Speicher für "StructGroesse" Elemente vom Typ "sFeld" allokiert. Der Name "StructGroesse" ist etwas unglücklich gewählt, aber prinzipiell sieht der Code nicht falsch aus.
I stand corrected.
Du hast vollkommen recht.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

LinkinMaster
Beiträge: 9
Registriert: Mo Jan 27, 2020 4:50 pm

Re: mehrdimensionaler Array aus eigenen Datentypen

Beitrag von LinkinMaster » Di Feb 18, 2020 2:09 pm

Vielen Dank, für die Hilfe!
Habe allerdings mein Programm etwas umgeschrieben in dem ich zwei, zwei Dimensionale Arrays nutze die ich quasi übereinander lege.
Ich werde aber noch mal versuchen, meine alte Version mit deiner Hilfestellung ans laufen zu bekommen und eine Rückmeldung geben.

Antworten