Reading a text file in C

Associate
Joined
22 Feb 2004
Posts
2,417
Location
Essex
trying to read a text file in C, should be simple, but its proving to be hard :(

the text file has multiple lines, which is possibly where im going wrong, keep getting Stream != NULL errors...


Does anyone have a quick bit of code to read a text file :)

Code:
int ReadINI() {
	char string;

	FILE *fp;
	fp = fopen("Countries.ini", "r");
	if (!fp) {
		printf("Error opening file");
		return 0;
	}
	while (!feof(fp)) {
		fscanf(fp,"%s",string);
		printf("%s",string);
	}
	fclose(fp);
	return 0;
}


edit: defined the size of my string, and all is well.

now it is interpreting spaces as line breaks :(
 
Last edited:
ok, new problem ;)

say i have several structures

player1
player2
player3
player4
...

is there a way to loop through each somehow

for (a=0;a<b;a++) {
playera.score = 0;
}
 
have you storted the first problem?

As for the second problem, I don't think you can. But if the player1-4 are the same type, just create an array of them, and loop through that.
 
Exactly the same way as you would a normal array, but use the structure name as the type. eg "<struct name> player[3]"
then access the elements as "player[0].score" ... "player[3].score"
 
Swanster said:
Exactly the same way as you would a normal array, but use the structure name as the type. eg "<struct name> player[3]"
then access the elements as "player[0].score" ... "player[3].score"


oh, i tried that and it wasn't having it


:edit: ah... forgot a * :o

cheers for your help m8 :D
 
Last edited:
no.

don't create an array of structs, create an array of pointers, each one pointing to one of the structs.
or add a pointer into the struct which you set to point to the next one, turning your structs into a linked list.

do it with pointers and then if needs be you can pass the pointer as an argument to a function.

try that with an array of structs and you'll be useing way more stack memory than you need for every function call.
 
ok, so i've made an array of structures and an array of pointers pointing to the structures.

What i want to do is pass the array of pointers into a new function and then, in the new function, alter the properties of each structure... not sure how to do this though

heres what i have so far:

Code:
typedef struct country {
	char *name;
	int border[8];
	int armies;
	int owner;
} Country;

LoadCountries(int total, Country *co);

Country *newcountry() {
  Country *p = malloc(sizeof *p);
  return p;
}

int main(void) {
	int a;
	int total = totalcountries();
	Country *country;

	country = malloc(total * sizeof *country);

	for (a=0;a<total;a++) {
		country[a] = *newcountry();
	}
	LoadCountries(total,&country);
	return 0;
}

LoadCountries(int total, Country *co) {
	co[1].armies = 6;
}
 
Last edited:
Code:
Country *country;

country = malloc(total * sizeof *country);

Here you are creating an array of counrty's, not an array of pointers to country's like you think.

Code:
Country *newcountry() {
  Country *p = malloc(sizeof *p);
  return p;
}

...

for (a=0;a<total;a++) {
		country[a] = *newcountry();
	}

Here you are assignning a country to another country. As the rhs country is malloc'd you are loosing the address of the memory and so this is a leak. Also the net effect of this assignment dosen't do anything as you are assigning an uninitialised country to another uninitialised country.


If you just want a dynamic (i.e. determined at run-time) array of country's, then just malloc the array and use it, there's no need for your newCountry() function:

Code:
#include <stdlib.h>

int main(void)
{
	int total = 20;
	Country* pCountries = NULL;

	pCountries = malloc( total * sizeof(*pCountrys) );
	LoadCountries( total,pCountries );

	return 0;
}

void LoadCountries(int total, Country *co)
{
	int i;
	for( i=0;i<total;i++)
	{
		co[i].armies = i;
	}
}

Remember you are going to need to allocate some memory for the name of your countries as well (at the moment you only have a char pointer).
 
thanks for your help guys

pretty much got the whole game sorted now (game of risk, if you're interested)

i've got all the attacking/defending/moving sorted, just one thing still bugs me.

i can't seem to read the name of the country into the structure:

Code:
typedef struct country {
	char *name;
	int border[10];
	int armies;
	int owner;
} Country;

int main(void) {
	int total = totalcountries();
	Country *country;
	country = malloc((total + 1 )* sizeof *country); //using + 1 as country ID starts from 1, not 0
	LoadCountries(total,country);
	return 0;
}

now, the LoadCountries function takes the ini file (here) and parses it to the structure. however, pch2 (using strtok) is meant to return the name of the current country. it does when used in a printf() statement, and if it is read back immediatly after it is assigned to co[n].name returns the current name.

However, if i try to access the name outside of the function, i get nothing. if i set the name to be "something" then i can read it outside of the function
Code:
int LoadCountries(int total, Country *co) {
	int i = 0;
	int border;
	int currentcountry;
	char *pch;
	char *pch2;

	char chrst[] = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
	int flag_countries = 0;
	int flag_continents = 0;
	char string[255];

	FILE *fp;
	fp = fopen("Countries.ini", "r");
	if (!fp) {
		printf("Error opening file");
	}
	
	while (!feof(fp)) {
		fscanf(fp,"%s",&string);
		
		if (strstr(string,"[Countries]") != NULL) {
			flag_countries = 1;
			flag_continents = 0;
		}
		if (strstr(string,"[Continents]") != NULL) {
			flag_countries = 0;
			flag_continents = 1;
		}

		if (flag_countries == 1) {
			pch = strtok (string,",");
			while (pch != NULL) {
				border = atoi(pch);
				if ((border != 0) && (!strstr(pch,"="))) {
					co[currentcountry].border[i] = border;
					i++;
				}
				if (strstr(pch,"=")) {
					currentcountry = border;
					pch2 = strpbrk(pch,chrst);

					
					co[currentcountry].name = pch2;
					
					i = 0;
				}
				pch = strtok(NULL, ",");
			 }
		}
	}
	fclose(fp);
	return 0;
}


entire source found here
 
Your main problem is that your countries name dose not have any memory associated with it, like I said before. In your LoadCountries() function you are pointing all your name char pointers at the local buffer 'string'. Not only is 'string' going to be constantly changing, as you read in new stuff from the file, which will effect all your coutries (as they are all pointing at the same bufer) but also as 'string' is a local variable it will be destoryed when LoadCountries() exits.

The simplist way would be to have a char array inside your country structure big enough to fit every countries name in:

Code:
typedef struct country
{
	char name[30]; //maxium country name length is 29
	int border[10];
	int armies;
	int owner;
} Country;

There are some other points about your code.

First is you dont free() your malloc()'d memory it is always good partice to do this.

Code:
while (!feof(fp)) {
...
}

Dont use feof() to control loop structures see here.

Code:
fscanf(fp,"%s",&string);

Dont use fscanf() if it fails it leaves the file stream in an undetermined state. Use fgets() then sscanf() the returned string. Also here your not putting any maxium size on the read in string which could lead to a buffer overflow, aways specify a maxium size for the read in string e.g. %255S.

Code:
fp = fopen("Countries.ini", "r");

In windows its best to always stick to binary file mode, "rb", text file mode is the default and will alter whats in your file as its read.

Code:
fclose(fp);

Here if your file fails to open you still try and close it.

When reading in your borders you dont mark the end of the array in any way, so how are you going to know how many borders a particular country has?

Your LoadCountries() function is abit messy, break up the loading of the countries and continents into different functions.

To demonstrate what I've said heres an exampe of how I would do it:

Code:
typedef struct country {
	char name[30];
	int border[10];
	int armies;
	int owner;
} Country;

void LoadCountries(int total, Country *co, FILE* fp)
{
	char buff[1024];
	char* pTok = NULL;
	int country = 1;

	while( fgets(buff,sizeof(buff),fp)!=NULL && country!=total )
	{
		int i=0;
		pTok = strtok( buff,"," ); 

		if( sscanf(buff,"%*d=%30[A-Za-z _]",co[country].name) == 1 )
		{
			while( pTok = strtok( NULL,"," ) )
			{
				co[country].border[i] = atoi(pTok);
				i++;
			}
			co[country].border[i] = -1; //mark the last border
			country++;
		}
	}
}

void LoadFile(int total, Country *co)
{
	char buff[1024];
	FILE* fp = fopen("Countries.ini", "rb");

	if( fp )
	{
		while( fgets(buff,sizeof(buff),fp)!=NULL )
		{
			if( strstr(buff,"[Countries]") != NULL ) LoadCountries(total,co,fp);
			if( strstr(buff,"[Continents]") != NULL ) ; //LoadContinents(total,co,fp);
		}

		fclose(fp);
	}
	else printf("File not found\n");
}

void PrintCountries( int total, Country* co )
{
	int i;
	for( i=1;i<total;i++ )
	{
		int j;
		printf("%s has borders with: ", co[i].name);
		for( j=0;co[i].border[j]!=-1;j++ )
		{
			int country = co[i].border[j];
			printf("%s,",co[country].name);
		}
		printf("\n");
	}
}

int main(void)
{
	int total = 42;
	Country *country;
	country = malloc((total + 1 )* sizeof *country); //using + 1 as country ID starts from 1, not 0
	LoadFile(total+1,country);

	PrintCountries(total+1,country);

	free(country);
	return 0;
}

You might not be familar with the format specifier in sscanf() "%*d=%30[A-Za-z _]". Here the * in %*d will cause sscanf() to skip pass a signed integer, and the %30[A-Za-z _] will match a string of 30 (including the null terminator) to A-Z or a-z or space or _.


As a side note (not really relavent as this is a learning exercise) win32 has functions for reading in *.ini files asthey are so comonly used in windows.
 
wow, thank you for your detailed reply, really appreciate it :)

this is the first thing i've done in C, and its been an experience :p

Will try those suggestions, and let you know how i get on
 
Back
Top Bottom