Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: I/O-ongelma

MikkoMMM [26.01.2011 23:20:38]

#

Minulla on sellainen pieni ongelma, että rivi

if ( !fgets(temp, sizeof(temp), pFile) ) { //Read a line of text
	printf("\nSome error with fgets.\n");
}

ilmeisesti palauttaa arvoa 0 jonkin aikaa sen jälkeen, kun FILE *pFile on vaihdettu osoittamasta config.txt:stä scripts/menus.txt:hen. Se siis onnistuneesti looppaa tuota readscript()-funktiota monta kertaa ja sitten lakkaa toimimasta. Linuxissa kaatuu segmentation faulttiin siinä vaiheessa kuitenkin jonkin aikaa vielä lukien sitä menus.txt:tä samalla päästellen "Some error with fgets."-debuggausviestiä kunnes päävalikossa painetaan return-näppäintä painikkeen "Start a new game" kohdalla, jatkaen edelleen riville riville 50 menus.txt:ssä, jonka sisältö on seuraavanlainen:

step 4 text "Dwarves: Dwarves are sometimes allied with humans but many of them fear goblins so"

ja kaatuen segmentation faulttiin, mutta Windowsissa jatkaa ohjelman suoritusta silti tosin tulostellen "Some error with fgets.".

gdb sanoo:
Program received signal SIGSEGV, Segmentation fault.
_IO_fgets (buf=0x805ad80 "\tstep 4 text \"Dwarves: Dwarves are sometimes allied with humans but many of them fear goblins so\"\r\n", n=180, fp=0x8006f73)
    at iofgets.c:52
52         _IO_acquire_lock (fp);
(gdb) bt
#0 _IO_fgets (buf=0x805ad80 "\tstep 4 text \"Dwarves: Dwarves are sometimes allied with humans but many of them fear goblins so\"\r\n", n=180,
   fp=0x8006f73) at iofgets.c:52
#1 0x0804be62 in readscript () at src/tom.c:788
#2 0x0804d1fb in main (argc=1, argv=0xbffff294) at src/tom.c:1050

Rivin 788 sisältö tom.c:ssä:

if ( !fgets(temp, sizeof(temp), pFile) ) { //Read a line of text

Rivin 1050 sisältö tom.c:ssä:

while( readscript() == 0 ); //An infinite loop that reads the open script file time after time.

Ja iofgets.c on standardikirjaston osa.

Valgrindilläkin tuli katsottua onko muistivuotoja, mutta mitään kovin isoa ei ainakaan ole.

Koko koodin voi ladata tästä (päivittelen sitä tosin vähän väliä): http://mandicor.net/tom/tom.zip

Tässä kuitenkin pätkä koodista, jossa hyvin todennäköisesti virhe on:

tom.h:

char temp[180];
char whatmenu[50];
char *pointer; //Used by parser
/* Tässä siis vain näitä parserin kannalta olennaisimpia muuttujia. */

tom.c:

/* Ongelman kannalta epäoleellisia funktioita poistettu */

/*
My own parser. Perhaps it would've been better to use LUA or something
like that but I didn't.

This function returns a pointer to the number that follows the string
it was called with. If unsuccessful, it will return integer value 0,
otherwize 1.
Great for checking if there is a certain character string in the line
it is reading.
*/
int getpointer(char string[]) {
	int i;
	int j = 0;
	int x = 1;
	int whatlen = strlen(string);
	int wherelen = strlen(temp);

	for( i = 0; i <= wherelen - whatlen; ++i) {
		for(j = 0; j < whatlen; ++j) {
			if(string[j] != temp[i+j]) break;
		}
		if(j == whatlen) break;
	}
	if(j != whatlen) return 0;

	for (j=0; j<=i; j++) {
		if (temp[j]=='\"') ++x;
		if ( j == i && !( x % 2 ) ) return 0; //Return if preceded by an uneven number of double quotes
	}
	pointer = temp + i;
	pointer += whatlen;
	if (*pointer + 1 > 'a' && *pointer + 1 < 'z') return 0;
	while (*pointer == '\"' || *pointer == '=' || *pointer == ' ') pointer++;
	return 1;
}

void readstring( char to[] ) { //This function copies the contents of the pointer to temp to the character string it was called with.
	int i = 0;
	do {
		to[i]=*pointer;
		pointer++;
		i++;
	} while (*pointer != '\"');
	to[i] = '\0';
}

/* Compares the pointer and a string. strcmp probably could do it too. */
int comparestrings( char to[] ) {
	int i = 0;
	int j = strlen(to);
	do {
		pointer++;
		i++;
		if ( i >= j ) return 1;
	} while (*pointer == to[i]);
	return 0;
}

/*
This function is a modification of stcmp function. It tests if the strings are the same with no additional characters following.
*/
int cstr(char from[], char to[]) {
	int i = strlen(to);
	if (!strcmp(from, to) ) { //compare strings
		if ( to[i] > 'a' && to[i] < 'z' ) return 0;
		if ( from[i] < 'a' || from[i] > 'z' ) return 1; //if no more characters present return 1
	}
	return 0;
}

/*
The Parser.
*/
int readscript() {
	SDL_EventState(SDL_KEYUP, SDL_IGNORE); //We don't support keyups at the moment.
	static char readfunction[50] = "config.txt"; //Which functions do we wish to read?
	FILE *pFile;
	int i, j, k, a, finalcolor, truthvalue, formeri, latteri, textpx, textpy, shallwehighlight = -1, wastext, braces;
	float menuitems; //Used in menu layout
	char tmpvar[80];
	char calc[4][30];
#ifdef withmusic
	static char whichmusic[50] = { '\0' };
#endif

	pFile = fopen (readfunction,"r");
	if (!pFile) {
		printf("Error opening file at %s.\n", readfunction);
		perror("");
	}

	while( 1 ){ //Because of how feof works
		if ( !fgets(temp, sizeof(temp), pFile) ) { //Read a line of text
			printf("\nSome error with fgets.\n");
		}
		if ( feof(pFile) ) break;
		if (getpointer("if")) { //At the moment conflicts with assignment statements so it should be the first one here.
			do {
				truthvalue = 1;
				do {
					readstring(calc[0]); //The former operand
					pointer++; //Double quotes.
					readstring(calc[2]); //The operation mark
					pointer++;
					readstring(calc[1]); //The latter operand
					pointer++;
					readstring(calc[3]); //The && and ||
					pointer++;
					formeri = latteri = 0;
					for (i = 0; i < vartabvalues; i++) {
						if ( cstr(calc[0], varTable[i].name ) )
							formeri = i;
						if ( cstr(calc[1], varTable[i].name ) )
							latteri = i;
					}
					if (latteri==0) latteri=1;

					/*
					This part is for constant integers.
					*/
					for (a = 0; a < 2; a++) {
						i = 0;
						if ((a == 0 && formeri == 0) || (a == 1 && latteri == 1)) {
							k = 0;
							while ((calc[a][i] < '0' && calc[a][i] != '\0') || (calc[a][i] > '9' && calc[a][i] != '\0')) {
								if (calc[a][i] == 'k') {k = 1; break;}
								if (calc[a][i] == 'K') {k = 2; break;}
								tmpvar[i] = calc[a][i]; //We still need the operators
								calc[a][i]=' ';
								i++;
							}
							if ((k == 0)) *varTable[a].var.d = atoi(calc[a]);
							if ((k == 1)) *varTable[a].var.d = calc[a][i+1];
							if ((k == 2)) *varTable[a].var.d = calc[a][i+1] + 208; //Keypad numbers
							for ( j = 0; j<i; j++) calc[a][j] = tmpvar[j]; //Put them back
						}
					}

					if( strstr( calc[2], "=" ) ) {
						if ( *varTable[formeri].var.d != *varTable[latteri].var.d ) { truthvalue = 0; break; }
					}

				} while ( strstr(calc[3], "&&") );
				if (truthvalue == 1) {
					printf("\nStatement is TRUE!\n");
					if ( !(fgets(temp, sizeof(temp), pFile) ) ) { //Because of the { ... } that would otherwise be skipped
						printf("Error in an if fgets");
						exit(0);
					}
					break; //Statement is still true after all &&s
				}
			} while ( strstr(calc[3], "||") );
		}
		if (!getpointer("if") && !getpointer("int") && !getpointer("char")) { //They are in conflict with assignment statements.
			for (i = 0; i < vartabvalues; i++) { //Read each variable defined in varTable
				if (getpointer(varTable[i].name)) {
					if ( varTable[i].type == 0 ) {
						*varTable[i].var.d = atoi(pointer);
						if (debug == 1) { printf("%s%d ", varTable[i].name, *varTable[i].var.d); }
					}
					if ( varTable[i].type == 1 ) {
						readstring(varTable[i].var.s);
						if ( cstr(varTable[i].name, "whatmenu" )) menuitem = 0;
						if (debug == 1) { printf(" %s", varTable[i].var.s); }
					}
					if ( varTable[i].type == 2 ) {
						*varTable[i].var.e = atof(pointer);
						if (debug == 1) { printf("%s%d ", varTable[i].name, *varTable[i].var.d); }
					}
					if ( varTable[i].type == 3 ) {
						varTable[i].var.i = atoi(pointer);
						if (debug == 1) { printf("%s ", varTable[i].name); }
					}
					if ( varTable[i].type == 4 ) {
						readstring(varTable[i].var.c);
						if (debug == 1) { printf(" %s", varTable[i].var.c); }
					}
				}
			}
		}
		if (getpointer("initializegame")) initialize();
		if ( getpointer("int") ) {
			if ( vartabvalues >= sizeof(varTable) ) { printf("TODO: Memory allocation for new variables. Exiting..."); exit(0); }
			readstring( tmpvar );
			for (i = 0; i < vartabvalues; i++) { //Read each variable defined in varTable
				if ( cstr(varTable[i].name, tmpvar) ) break;
			}
			if ( i == vartabvalues ) { //This prevents duplicates.
				strcpy (varTable[vartabvalues].name, tmpvar);
				varTable[vartabvalues].var.i = atoi(pointer + 1);
				varTable[vartabvalues].type = 3;
				printf("A new variable defined: %s %d\n", varTable[vartabvalues].name, varTable[vartabvalues].var.i);
				vartabvalues++;
			}
		};
		if ( getpointer("char") ) {
			if ( vartabvalues >= sizeof(varTable) ) { printf("TODO: Memory allocation for new variables. Exiting..."); exit(0); }
			readstring( tmpvar );
			for (i = 0; i < vartabvalues; i++) { //Read each variable defined in varTable
				if ( cstr(tmpvar, varTable[i].name) ) break;
			}
			if ( i == vartabvalues ) {
				strcpy (varTable[vartabvalues].name, tmpvar);
				while (*pointer == '\"' || *pointer == '=' || *pointer == ' ') pointer++;
				readstring( varTable[vartabvalues].var.c );
				varTable[vartabvalues].type = 4;
				printf("A new character array defined: %s %s\n", varTable[vartabvalues].name, varTable[vartabvalues].var.c);
				vartabvalues++;
			}
		};
		if (getpointer("run")) readstring(readfunction);
		if (getpointer("exit")) return 1; //exit(1);
		if (getpointer("caption")) { readstring(tmpvar); SDL_WM_SetCaption(tmpvar, NULL); } //The name of the window
		if (getpointer("{")) {
			if (getpointer("createmenu") && comparestrings(whatmenu) ) {
				divisor = 0, size = 1, color = 1, centered = 0, xcoords = 0, xdivis = 0, step = 1, menuitems = 0, wastext = 0;
				SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 ); //This will clear the screen
			}
			else {
				braces = 1;
				while ( braces ) {
					if ( !(fgets(temp, sizeof(temp), pFile) ) ) { printf("Error in a function fgets"); exit(0); }
					if ( getpointer("{") ) braces++;
					if ( getpointer("}") ) braces--;
				}
			}
		}
		if (getpointer("finishtext")) {
			SDL_Flip(scrn);
			if ( menuitem > shallwehighlight ) menuitem = 0;
			if ( menuitem < 0 ) menuitem = shallwehighlight;
			shallwehighlight = 0;
		}
		if (getpointer("text")) {
			textpx = textpy = 0;
			menuitems += step;
			if ( color < 0 ) {
				if (wastext == 0) shallwehighlight++;
				wastext = 1;
				if (shallwehighlight == menuitem) finalcolor = 1;
				else finalcolor = 0;
			}
			else finalcolor = color;

			readstring(tmpvar);
			if (xdivis<=0) textpx = xcoords * fsz;
			else textpx = RES_X / xdivis + xcoords * fsz;
			if ( divisor > 0 ) textpy = RES_Y / divisor * menuitems;
			if (centered) centeredtext(finalcolor, tmpvar, textpy, fsz * size);
			else fixedtext(finalcolor, tmpvar, textpx, textpy, fsz * size);
		}
		else wastext = 0;
		if (getpointer("waitpressedkey")) {
			status = SDL_WaitEvent(&event);	//wait indefinitely for an event to occur
			if ( !status ) {		//Error has occurred while waiting
				printf("SDL_WaitEvent error: %s\n", SDL_GetError());
				return 0;
			}

			if ( event.type == SDL_KEYDOWN ) keyp = event.key.keysym.sym; //Copy the just pressed key's value to keyp

			if ( event.type == SDL_QUIT ) {
				SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
				centeredtext(1, "Really quit? y/n", RES_Y / 2 - fsz*1.5, fsz*3);
				if (wait_y()) exit(1);
				pois=1;
			}
			if ( keyp == SDLK_ESCAPE ) {
				SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
				centeredtext(1, "Really quit? y/n", RES_Y / 2 - fsz*1.5, fsz*3);
				if (wait_y()) exit(1);
				pois=1;
			}
			if ( keyp == SDLK_h ) { ingamemenu(); SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 ); SDL_Delay(delay); } //List of commands
			if ( keyp == SDLK_0 ) settings(); //Settings
			if ( keyp == SDLK_RETURN ) {
				if ( cstr(whatmenu, "main") ) {
					if ((menuitem == 2)) { ingamemenu(); }
					if ((menuitem == 4)) exit(1);
				}
			}
			if (event.type == SDL_VIDEORESIZE) {
				leveys=event.resize.w+leveys-RES_X;
				korkeus=event.resize.h+korkeus-RES_Y;
				RES_X = event.resize.w;
				RES_Y = event.resize.h;
				resize();
			}
		}
		if (getpointer("verticalarrows")) {
			if ( keyp == SDLK_UP ) menuitem--;
			if ( keyp == SDLK_KP8 ) menuitem--;
			if ( keyp == SDLK_DOWN ) menuitem++;
			if ( keyp == SDLK_KP2 ) menuitem++;
		}
		if (getpointer("horizontalarrows")) {
			if ( keyp == SDLK_LEFT ) menuitem--;
			if ( keyp == SDLK_KP4 ) menuitem--;
			if ( keyp == SDLK_RIGHT ) menuitem++;
			if ( keyp == SDLK_KP6 ) menuitem++;
		}
#ifdef withmusic
		if (playsound!=0) {
			if (getpointer("changemusic")) {
				readstring(tmpvar);
				if ( strcmp(whichmusic, tmpvar ) ) {
					strncpy (whichmusic, tmpvar, strlen(tmpvar));
					if (debug == 1) printf("\nChanged music.\n");
					sound = Mix_LoadMUS(tmpvar);
					if(sound == NULL) {
						printf("Unable to load music file %s: %s\n", tmpvar, Mix_GetError());
						playsound=0;
					}
					if(musicPlaying<1) {
						if(Mix_PlayMusic(sound, 0) == -1)
						{
							printf("Unable to play music file: %s\n", Mix_GetError());
						}
					}
					musicPlaying = 1;
				}
			}
		}
#endif
	}
	if ( fclose (pFile) != 0 ) printf("Error when closing file.\n");
	return 0;
}


/*
The main loop. This is where it all begins.
*/

int main(int argc, char *argv[]){
	while( readscript() == 0 ); //An infinite loop that reads the open script file time after time.

/* Poistettu command line optionit ja loppu main:sta */

Ensimmäinen itse koodaamani C-ohjelma, muuten.

Metabolix [27.01.2011 00:09:00]

#

Kaatuminen valikkoon mennessä johtuu niinkin perinteisestä syystä kuin puskurin ylivuodosta readstring-funktiossa. Sinun pitää siis ilmoittaa readstring-funktiolle puskurin pituus ja huolehtia, ettei tekstiä tule luettua sen enempää. Itse toteutin tarkistuksen testimielessä tällaisella makrolla:

#define readstring2(x) readstring_(x, (sizeof(x) == sizeof(char*)) ? 50 : sizeof(x))

(Makron toiminta perustuu oletukseen, että ainoat käyttämäsi osoittimet ovat noissa varTable[i].var.s -kohdissa ja että niihin mahtuu 50 merkkiä. Makro ei sovi lopulliseksi korjaukseksi!)

Ohjelman lopussa sattuva kaatuminen johtuu siitä, että vapautat joitain pintoja moneen kertaan. Yksinkertaisin korjaus tähän on, että asetat aina vapautuksen jälkeen osoittimeen arvon NULL. Itse toteutin tämän testimielessä koodiisi korvaamalla kaikki SDL_FreeSurface-kutsut seuraavalla makrolla:

#define SDL_Safe_FreeSurface(x) (SDL_FreeSurface(x), (void)(x = 0))

Resurssivuotoja on ainakin kuvien latauksissa. Jos avaat tiedoston funktiolla SDL_RWFromFile, se pitää vastaavasti sulkea funktiolla SDL_RWclose. Viisainta olisi käyttää kuvien lataukseen suoraan funktiota IMG_Load ilman mitään omia kikkailuja.

MikkoMMM [27.01.2011 02:15:13]

#

Jaa-ah. Ei ihan mitä gdb antoi ymmärtää. Tosiaan nuo tekstit ovat huomattavasti pidempiä kuin bufferi.

Metabolix [27.01.2011 16:09:53]

#

Aika harvoin sattuu virheitä, jotka gdb osaisi suoraan ilmoittaa perin pohjin; yleensä pitää osata tulkita ja tutkia tarkemmin. Puskurin ylivuoto selvisi siten, että kaatumisen jälkeen palasin up-komennolla readscript-funktioon ja tarkistin fgets:lle annetut parametrit. Muuttujan pFile arvo oli kerta kaikkiaan sopimaton osoittimelle. (Jos kokemus ja pohjatiedot eivät riitä, sopimattomuuden voi tarkistaa tulostamalla: print *pFile ilmoittaa, että muistia ei voida lukea.) Muuttujien oudot arvot taas lähes aina johtuvat mokailusta taulukoiden pituuden kanssa, ja sinun ohjelmassasi tästä ajatuksesta on aika lyhyt matka omatekoiseen readstring-funktioon.

Ohjelman lopussa Linuxilla tulee ilmoitus "double free or corruption", jonka tulkintaan ei tarvita edes gdb:tä, koska tyypillisiä syitä on vain kaksi: ilmoituksessa mainittu kaksoisvapautus (kuten sinulla) ja puskurin yli- tai alivuoto.

Pari muuta suunnitteluvirhettä jäi mainitsematta: Jos tiedostoa ei saada auki, on aika tyhmää mennä ikuiseen silmukkaan, jossa sitä yritetään lukea. Loogisempaa olisi keskeyttää funktio saman tien. Vastaavasti jos fgets menee pieleen, break olisi paljon mielekkäämpi ratkaisu kuin silmukan jatkaminen.

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta