C Programmierbeleg in C - mit einigen Problemen

Nero1

Captain Pro
Registriert
Nov. 2009
Beiträge
3.833
Hi Leute,

folgendes Problem:

Schreibe für mein duales Studium Informationstechnik einen Programmierbeleg, der sich um das Thema Hangman drehen soll. Dabei gibt es verschiedene Anforderungen wie ein Log in ner Binärdatei und solche Späße. Da das aber insgesamt zu umfangreich ist und derzeit nicht gebraucht wird, würde ich benötigte Informationen rausgeben wenn wir sie brauchen ;)

Wie schon im Titel angedeutet soll das hier eine Art Dauerauftrag sein, einfach um nicht jede Frage, die sich mir ergibt, in 20 verschiedene Posts zu packen, das nervt nur und wird unübersichtlich :D
Wer also bereit ist zu helfen und sich mit C ein wenig auskennt darf gerne abbonieren und auf dem Stand der Dinge bleiben, würde mich freuen ;)


Nun aber zum eigentlichen Sachverhalt der mich derzeit beschäftigt...vorab erstmal der Code:

Die Funktionen:
Code:
#include "Deklarationen.h"

//Bestimmung der Anzahl von Wörtern (zeilenweise)
int WorteAnzahl(FILE *datei)
{
	int i=0;
	char puffer[81];
	char *ret;

	while((ret=fgets(puffer,80,datei))!=NULL)
	{		
		i++;
	}
	return i;
}

//Anhand einer Zufallszahl Wort in Datei bestimmen
void Zufallswort(FILE *datei, int zufZahl)
{
	int i=0;
	char puffer[81];
	char *ret;

	while((ret=fgets(puffer,80,datei))!=NULL)                          //= "der Befehl" :P (Bedeutung siehe unten)
	{		
		i++;
		if(i==zufZahl)
		{
			puts(puffer);
			return;
		}
	}
}

Und die Main():

Code:
#include "Deklarationen.h"

int main(void)
{
	//--------------------------------------------------BEGINN---------------------------------------------------//
	
	FILE *fWort;
	
	fWort=fopen(woerter,"r");
	if(fWort==NULL)
	{
		printf("Fehler beim Zugriff auf %s",woerter);
		return 1;
	}

	//-----------------------------------------------Wort auswählen----------------------------------------------//

	time_t t;
	time(&t);
	srand((unsigned int)t);				        //Zufallszahlen - Generator initialisieren

	int anzWort;
	int x=WorteAnzahl(fWort);
	
	anzWort=(rand()%(x))+1;					//Zufallszahl für Wort finden

	Zufallswort(fWort,anzWort);

	


	//----------------------------------------------------ENDE---------------------------------------------------//

	if(!(fclose(fWort)));
	else
		printf("Fehler beim schließen der Datei %s!",fWort);

	fflush(stdin);
	getchar();
	return 0;
}

Ich bin noch am Anfang, also nicht wundern ;) Momentan ist das Ziel ein Wort aus einer .txt - Datei zufällig auszuwählen, also per Random anhand der Anzahl der Wörter in der Datei.

Was das Problem ist:

Wenn die Funktion WorteAnzahl durchgelaufen ist ist der Dateistream von fWort bei NULL. Somit wird der Befehl (siehe oben), der das Wort ausgibt bzw. sucht, gar nicht erst ausgeführt. Ich habe dazu schon mit einem Kollegen gesprochen, der meinte man könnte das durch CALL BY VALUE lösen (ein Schließen und Öffnen der Datei wäre auch möglich, aber sinnlos und nur als Notlösung zu betrachten, ist mir nämlich auch schon in den Sinn gekommen :P).
Somit würde ich ja dann auch nicht mit den Speicherzellen arbeiten sondern nur mit dem Inhalt des entsprechenden Speichers.

Nun hab ich aber keinen Schimmer, wie ich ein FILE *datei in ein Value umwandeln soll o.O Geht das irgendwie mit get() set()? Oder muss ich da ganz anders rangehen?

Hoffe ihr könnt mir da helfen :)

LG Nero


P.S.:

Gearbeitet wird mit Visual Studio 2010 auf Arbeit oder 2012 bei mir zuhause. Daher ist das ein C++ - Compiler. Einige wenige C++ Funktionen dürfen wir nutzen, aber dazu mehr wenn es soweit ist.
 
Setze die Leseposition wieder zurück an den Anfang. fseek() hilft.
 
Danke für die schnelle Antwort, klappt bestens :) Manchmal scheitert man an den einfachsten Dingen^^ Vielen Danke!

LG
 
fseek() war schon bzgl. der Frage der richtige Tipp. fflush(stdin) ist ne blöde Idee. Generell kannst du nur Ausgangsströme "flushen" und so ist das Stückchen Code "undefined behaviour". Auf manchen Plattformen gibt es für die gewünschte Semantik ne Lösung (z.B. fpurge). Auf anderen kannst du händisch was basteln.
 
aphex.matze schrieb:
fflush(stdin) ist ne blöde Idee. Generell kannst du nur Ausgangsströme "flushen" und so ist das Stückchen Code "undefined behaviour". Auf manchen Plattformen gibt es für die gewünschte Semantik ne Lösung (z.B. fpurge). Auf anderen kannst du händisch was basteln.

Also wir haben es so gelernt mit fflush(stdin) den Eingabepuffer zu leeren^^

Hab grad ma gegoogelt und scheint ja wirklich net in Standard C definiert zu sein o.O Kann man das dann einfach weglassen und sagen ich mach fflush bei meinen Dateistreams, in dem Fall einfach fflush(fWort)? Reicht das aus?

LG
 
Was willste mit dem flush() denn eigentlich erreichen? Im Codebeispiel kann es auf jeden Fall weg.
 
Hab grad nochmal ein wenig im Skript geblättert, zitier mal nur der Vollständigkeit halber:

"Der ANSI C-Standard schreibt nicht vor, wie die Funktion fflush() auf zum Lesen geöffnete Dateien, also auch auf den Stream stdin zu reagieren hat. Das Verhalten ist somit nicht definiert." Obwohl es unter Windows trotzdem funzt^^

Naja was will ich mit erreichen, wir hatten es halt immer so verwendet bis jetz um den Puffer zu leeren...Im Prinzip braucht mans ja "nur" um die restlichen Daten die im Puffer liegen zu löschen bzw in die Datei zu schreiben. Ob wir das nun am Anfang der Einfachheit halber beigebracht bekommen haben oder ob da iwelche Probleme auftreten können wenn der Puffer noch "besetzt" is weiß ich net...

Aber wie ich heraushöre brauch ichs sonst net wirklich (situationsbedingt)...Wieder was gelernt^^
 
Ich verstehe den Sinn von fflush() nicht wirklich. Die entsprechenden Daten zu handhaben ist Aufgabe des Betriebssystems
und das weiß in der Regel besser, als der Programmierer, wann es sinnvoll ist, Daten im Speicher zu halten, oder auf das Gerät
zu schreiben. Für den Programmierer ist es auch völlig transparent.

Gut möglich, dass ich das falsch verstanden habe und man möge mich ggf. korrigieren.
 
asdfman schrieb:
Ich verstehe den Sinn von fflush() nicht wirklich. Die entsprechenden Daten zu handhaben ist Aufgabe des Betriebssystems
und das weiß in der Regel besser, als der Programmierer, wann es sinnvoll ist, Daten im Speicher zu halten, oder auf das Gerät
zu schreiben. Für den Programmierer ist es auch völlig transparent.
Das OS weiß vielleicht manches besser aber nicht alles. Es kennt z.B. die Intension des Programmierers nicht. Der zu "flushende" Dateideskriptor könnten auch sonstwas sein und vielleicht möchte der Programmierer nicht, dass Schreiboperationen länger gepuffert werden. Ich möchte vielleicht ein Logbuch sofort auf den Datenträger durchschreiben, weil das eingebettete System mal spontan von der Spannungsversorgung getrennt werden könnte (Akku fällt aus dem Telefon).
Ergänzung ()

Nero1 schrieb:
Naja was will ich mit erreichen, wir hatten es halt immer so verwendet bis jetz um den Puffer zu leeren...Im Prinzip braucht mans ja "nur" um die restlichen Daten die im Puffer liegen zu löschen bzw in die Datei zu schreiben. Ob wir das nun am Anfang der Einfachheit halber beigebracht bekommen haben oder ob da iwelche Probleme auftreten können wenn der Puffer noch "besetzt" is weiß ich net...

Aber wie ich heraushöre brauch ichs sonst net wirklich (situationsbedingt)...Wieder was gelernt^^
Lass' es einfach weg und schreib' nix auf, was nicht wirklich verstanden ist :) Wenn du auf was neues stößt, schau nach, was es macht und warum es da ist.
Ich würde dir auch Linux für die C-Programmierung ans Herz legen. Dort lernt man die Details und Hintergründe imho am besten und das Handbuch ist immer in der Nähe (man).
 
Hi, ich bins mal wieder :D

Neues Problem: Möchte zwei char-Arrays vergleichen, damit bei meinem Hangman-Spiel der Durchlauf des Prüfens der Eingabe abbricht. Sprich ich ersetze immer wieder "-" mit dem richtigen Buchstaben bis das Wort "-"-frei ist sozusagen.

Bsp: Garten

- - - - - - => G

G - - - - - => a

Ga - - - - => usw.

Wahlweise kann man dann noch das komplette Wort eingeben:

G - - - - - => Garten

Garten => Gewonnen

Das soll mit folgendem Code durchgeführt werden:

Code:
void EingabePruefen(int stringlength,char *temp,char *suchwort,int modus)
{
	//Ratevorgang durchgehen

	int i,j,k;
	char charEingabe[28];    //genauso lang wie Suchwort
	
	for(i=0;i<26;i++)
	{
		fflush(stdin);
		printf("\nEingabe (1 Buchstabe oder L\224 \bsungswort): ");

		scanf("%s",charEingabe);

		if(strcmp(charEingabe,suchwort)==0)
		{
			printf("Herzlichen Gl\201 \bckwunsch! Gewonnen!");
		}
		else
		{
			for(j=0;j<stringlength;j++)
			{
				if(modus==1)
				{
					if(charEingabe[0]==suchwort[j])
					{
						temp[j]=suchwort[j];
					}
				}
				else if(modus==-1)
				{
					if(tolower(charEingabe[0])==tolower(suchwort[j]))
					{
						temp[j]=suchwort[j];
					}
				}
			}
			printf("\t\t\t\t\t");
			for(k=0;k<stringlength-1;k++)
			{
				printf("%c",temp[k]);
			}
			fflush(stdin);			//Flushen des eingehenden Streams, da sonst doppelte Ausgabe des temp[k] möglich
		}
	}
	printf("\n\nAnzahl der Versuche: %d",i);
	getchar();
}

Dabei wird
Code:
if(strcmp(charEingabe,suchwort)==0)
		{
			printf("Herzlichen Gl\201 \bckwunsch! Gewonnen!");
		}

immer herzlichst übersprungen, selbst wenn ich bei z.B. Fahrrad als gesuchtes Wort Fahrrad eingebe. Im Endeffekt nimmt es das dann an als F und nicht als string.

WARUM?

Hatte es vorher Testweise mit zwei anderen Arrays probiert und da klappte es super...

P.S.: bitte nicht über das fflush(stdin) aufregen, ich weiß nicht definiert und so, aber ich denke nicht, dass es da Probleme geben sollte, ansonsten kann ich das immer noch mit der korrekten Version ersetzen, das is nich das Problem ;)
 
Hab durch Zufall gestern mit nem Freund von mir geredet, dem das auch aufgefallen is, aber vielen Dank dass du geantwortet hast. Echt fies von strcmp :D

Hatte mir dann vorgeschlagen mit fgets in ein Feld zu schreiben und mit sscanf auszulesen, sollte ja nich falsch sein oder?
 
2 Vorschläge:

1) Wenn ihr schon das const-Schlüsselwort verwenden dürft, würde ich dir empfehlen, deinen suchwort-Parameter const zu machen; also

Code:
void EingabePruefen(int stringlength,char *temp,const char *suchwort,int modus)

Damit wird jedem Programmierer auf einen Blick klar (selbst ohne, daß er den Rumpf der Funktion anschauen muß), aha, die Zeichenkette, auf die der suchwort-Pointer zeigt, wird von dieser Funktion nicht verändert. Und solltest du später deine Funktion erweitern und es fälschlicherweise doch versuchen, dann würde dir der Compiler gleich freundlicherweise auf die Finger klopfen und dich auf deinen Fehler aufmerksam machen.

2) Ich würde dir auch dazu raten, hier noch einige Fehlerüberprüfungen bezüglich der Längen der verschiedenen Zeichenketten einzubauen. Was ist, wenn mal jemand ein Suchwort angibt, das z.B. 50 Zeichen lang ist? Dein charEingabe-Array hat nur Platz für 28 (inklusive der terminierenden Null) -> Zack, Speicherüberschreiber! Du mußt ja deine Funktion nicht gleich notwendigerweise dazu ertüchtigen, mit solchen Fällen problemlos umzugehen, aber zumindest würde ich solche Fälle irgend wie abfangen und dann mit einer Fehlermeldung aussteigen.

In C könntest du dazu z.B. assert() verwenden. Mit assert() kann man eine zu testende Bedingung angeben, und wenn die nicht erfüllt ist, dann wird das Programm mit einer kurzen Fehlermeldung abgebrochen. Das könnte in deiner Funktion z.B. so aussehen:

Code:
void EingabePruefen(int stringlength,char *temp,char *suchwort,int modus)
{
    char charEingabe[28]; //genauso lang wie Suchwort

	assert( stringlength < sizeof( charEingabe ) && stringlength >= 0 );
	
	// ...
}

P.S. In C ist assert() glaube ich in <assert.h> definiert.
 
Wow, danke für die Tipps, das werd ich gleich mal probieren wenn ich das nächste mal dran sitze. Vielen Dank dafür, genau solche Tipps find ich am nützlichsten :)

Da könnte ich das einlesen ja theoretisch gleich mit dem assert verbinden, dann kann ich den Rest so lassen da es ja sonst eh vorher abbricht...coole Sache ;)

LG
 
Nein, das solltest du nicht tun. Wenn du nach dem Einlesen assert() aufrufst, rettet dich das nicht davor, dass der Puffer schon
vorher übergelaufen sein könnte. Und dann ist das Kind schon ins Wasser gefallen. Sinnvoller wäre es, einen Pufferüberlauf von vorne
herein auszuschließen. Zum Beispiel so:

Code:
static char *get_line(FILE *fp) {
	size_t size = 0, last = 0;
	char *buf  = NULL;

	do {
		size += BUFSIZ;
		buf = (char*)realloc(buf, size);
		fgets(buf + last, size, fp);
		last = strlen(buf) - 1;		
	} while (!feof(fp) && buf[last]!='\n');
	buf[last] = '\0';

	return buf;
}
 
Zuletzt bearbeitet:
Nero1 schrieb:
Da könnte ich das einlesen ja theoretisch gleich mit dem assert verbinden, ...
LG

assert(s) sind reine debugging/entwicklungshilfen! assert benutzt man um zu testen, ob an dieser stelle der status des programms so ist, wie du es erwartest und falls nicht, soll es dein programm terminieren und dich informieren.

es ist aber mit nichten eine funktion, die du in deine programmlogik einbinden solltest. auf produktiv systemen wern normalerweise alle asserts deaktiviert. es ist eine reine entwicklerhilfe!
 
Und asserts sind gefährlich, weil diese meist als Makro definiert sind und im Release-Modus dann zu "" ersetzt werden.
Beispiel:
Code:
assert(DoSomethingWithReturnValue());
Wird im Debugmodus zu
Code:
ASSERTFUNC(DoSomethingWithReturnValue(),"DoSomethingWithReturnValue()");
und im Release zu
Code:
 
Hancock schrieb:
Und asserts sind gefährlich, weil diese meist als Makro definiert sind und im Release-Modus dann zu "" ersetzt werden.
Beispiel:
Code:
assert(DoSomethingWithReturnValue());
Wird im Debugmodus zu
Code:
ASSERTFUNC(DoSomethingWithReturnValue(),"DoSomethingWithReturnValue()");
und im Release zu
Code:

Deshalb sind sie aber nicht "gefährlich" ... das klingt so, als wäre es eine schlechte Idee, sie zu benutzen. Man muß sie nur korrekt benutzen, und das bedeutet eben, daß man keine für den Ablauf des Programms relevanten Anweisungen in assert's packen darf.
 
Hancock schrieb:
Und das ist gefährlich, weil man implizit auf etwas achten muss, grad Programmieranfängern ist sowas nicht unbedingt klar.

Daß man beim Programmieren darauf achten muß, die diversen von der jeweilgen Sprache angebotenen Konstrukte korrekt einzusetzen, versteht sich von selbst.
Ich gebe allerdings zu, daß ich diese mögliche Fehlerquelle hätte erwähnen sollen, als ich dem OP verschlug, asserts zu nutzen. Trotzdem halte ich den freizügigen Einsatz von asserts für eine sehr gute Idee. Es ist eine der besten Ansätze, Fehler im Code so früh wie möglich zu entdecken.

P.S. Natürlich sollte man nicht auf assert allein setzen. In meinem Code nutze ich meistens eine selbstgestricktes HARD_ASSERT, das in einem Debug-Build lediglich auf das normale asserr() zurückfällt und in einem Release-Build stattdessen eine von std::exception abgeleitete Exception wirft. Letzteres ist natürlich C++-spezifisch ... für C-Code müßte man sich was anderes überlegen.
 
Zuletzt bearbeitet:
Zurück
Oben