Šta je novo?

Pomoć za C

ivan90BG

Cenjen
Učlanjen(a)
07.01.2010
Poruke
1,045
Poena
199
Treba mi pomoć za jedan zadatak iz C-a.

Ubacujem tekstualni fajl u char buffer. A onda treba taj buffer da podelim u onoliko char* (pointera) koliko ima redova teksta.

Pretpostavljam da treba da napravim niz char* (kar star-ova). Svaka stavka niza da bude pointer ka stringu koji predstavlja jedan red iz tekstualnog fajla.

Ali kako da izvučem pojedinačne redove? Sigurno treba da pravim neku petlju, a u svakoj iteraciji kupim karaktere do sledećeg EOL karaktera. Kako to da izvedem?
 
Moraš prvo da izbrojiš koliko ima redova, pa da rezervišeš niz char *, pa da onda ponovo prođeš kroz ceo buffer i dodeliš svakom članu niza poziciju početka novog reda, mada ako koristiš fgets() funkciju koja i ovako čita red po red iz fajla, možeš dok čitaš da brojiš, pa posle samo da prođeš kroz bafer jednom.

A možeš i da pretpostaviš neki maksimalan broj redova pa da odmah rezervišeš memoriju.
 
A kako to fgets() čita red po red. Kolko ja znam ona kao parametre prihvata buffer, FILE* i veličinu bafera.

Da li možda ona zapisuje sadržaj fajla u bafer na specijalan način?

Koiko vidim broj EOL karaktera je 10, a terminacioni broj je 0. Da li funkcija fgets() stavlja u buffer i EOL karaktere posle svakog reda iz originalnog tekstualnog fajla?
Možda bih mogao to da iskoristim, tako što bih u nekoj petlji prvo brojao koliko ima EOL karaktera, a onda u drugoj upisivao pointere ka char-ovima posle EOL karaktera, dok bi se EOL karakteri istovremeno zamenjivali terminacionim, čime bih dobio poseban "string" za svaki red u memoriji, sa sve nizom pointera ka početcima svakog stringa.

Dal može ovako?
 
Što se fgets() funkcije tiče, lepo piše u MSDNu (a koliko se sećam tako je i u C-u):

The fgets function reads a string from the input stream argument and stores it in str. fgets reads characters from the current stream position to and including the first newline character, to the end of the stream, or until the number of characters read is equal to n – 1, whichever comes first. The result stored in str is appended with a null character. The newline character, if read, is included in the string.



Evo ti rešenje, pod pretpostavkom da se buffer u koji je učitan fajl posle više ne koristi - tako da se svi stringovi u stvari čuvaju i dalje u baferu, a pointeri samo pokazuju na početak reda. Kompajlirano u Visual C++-u, ali ubeđen sam da je sve C compliant. :D
Možeš da ubaciš proveru da li je fajl uspešno otvoren i eventualno da se ime fajla uzima iz argv.

Kod:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <malloc.h>

int main(int argc, char *argv[])
{
	FILE *input_file = NULL;
	char *txt_buffer = NULL;
	char **new_line_ptr = NULL;
	unsigned int new_line_count = 0;
	unsigned int input_file_size = 0;
	struct stat file_stat;
	size_t bytes_read;
	unsigned int i, j;

	//proverava velicinu fajla
	stat("test.txt", &file_stat);
	input_file_size = file_stat.st_size;
	
	//rezervise dovoljno memorije za txt_buffer
	txt_buffer = (char *)malloc((input_file_size + 1) * sizeof(char));
	if(txt_buffer == NULL)
	{
		printf("Not enough memory for txt_buffer! Exit with 1...");
		return 1;
	}

	//otvara fajl u binarnom modu, čita iz njega i zatvara ga
	input_file = fopen("test.txt", "rb");
	
	bytes_read = fread(txt_buffer, 1, input_file_size, input_file);
	txt_buffer[bytes_read] = 0;

	fclose(input_file);

	//broji koliko ima znakova za novi red
	for(i=1;i<input_file_size;i++)
	{
		if( (txt_buffer[i] == 0x0A) && (txt_buffer[i-1] == 0x0D) )
			new_line_count++;
	}


	new_line_ptr = (char **) malloc(new_line_count * sizeof (char *));
	
	if( new_line_ptr == NULL)
	{
		printf("Not enough memory for new_line_ptr. Exiting with 1...");
		free(txt_buffer);
		return 1;
	}
	
	//inicijalizuje sve pointere na NULL
	for(i=0;i<new_line_count;i++)
		new_line_ptr[i] = NULL;

	
	//prvi red je od pocetka buffera
	new_line_ptr[0] = txt_buffer;
	j = 1;
	
	for(i=1;i<input_file_size-1;i++)
	{
		if( (txt_buffer[i] == 0x0A) && (txt_buffer[i-1] == 0x0D) )
		{
			new_line_ptr[j++] = txt_buffer + i + 1;
			txt_buffer[i] = 0;
			txt_buffer[i-1] = 0;
		}
	}



	//Ispisivanje svih redova

	for(i=0;i<new_line_count;i++)
		printf("%02d. %s\n", i, new_line_ptr[i]);


	//Cleanup
	if( txt_buffer != NULL)
		free(txt_buffer);

	if( new_line_ptr != NULL)
		free(new_line_ptr);

	return 0;
}
 
Poslednja izmena:
:banana: Fala prijatelju.

Radi kod super. Samo ti nije baš skroz C compliant. Malloc.h nije deo standardne C biblioteke (malloc, calloc, realloc i free se nalaze u stdlib.h) :p A ja radim sa potpuno standardnim GCC-om. :D

Ne znam odakle ti ova dva sys-a, ali to prolazi normalno na Mac OS X-u, a ništa ne piše na Wikipediji. :p

Ali ima nešto što ne prolazi, a što sam primetio i pre nego što sam isprobao, a to je ovo:
Kod:
(txt_buffer[i] == 0x0A) && (txt_buffer[i-1] == 0x0D)
To je zato što na Windows-u važi da je u txt fajlovima na kraju reda LF+CR, dok je na Unix-u i Unix-like samo LF, tako da sam obrisao drugi deo provere (i drugu dodelu nule tamo gde je bila) i radi normalno. Tako da je u ovom sličaju ipak bilo potrebno malo portovati kod. ,)

Ipak super ti je rešenje - dekalrisanje i alociranje pointera kao (char **), a onda koriščenje kao niz. Moraću ja malo da vežbam ove mahinacije sa memrijom. :D

p.s. obrisao sam one provere alociranja jer nema šanse da nema dovoljno memorije. (učitavaće se 1 megabajt ili manje teksta). :smoke:
 
Poslednja izmena:
p.s. obrisao sam one provere alociranja jer nema šanse da nema dovoljno memorije. (učitavaće se 1 megabajt ili manje teksta). :smoke:

Uvek ima šanse da nema dovoljno memorije. Pri rezervisanju memorije se uvek proverava da nisi završio sa NULL pointerom.

Ako se radi o Unix txt fajlovima (samo 0x0A na kraju reda), treba promeniti i da for petlje kreću od početka bafera da bi se pokrio slučaj kada imaš odmah newline karakter u prvom redu.

Što se tiče header fajlova (<sys/stat.h>, <sys/types.h>) koliko ja znam postoje na Unixu. Na *nix sistemima imaš i unistd.h u kome imaš funkcije za rad sa fajlovima (open, read, write).

A mogao bi i da napraviš verziju koja korektno radi i sa jednim i sa drugim fajlovima. :)

Kod:
//broji koliko ima znakova za novi red
	for(i=0;i<input_file_size;i++)
	{
		if( txt_buffer[i] == 0x0A )
			new_line_count++;
	}

Kod:
for(i=0;i<input_file_size-1;i++)
	{
		if( txt_buffer[i] == 0x0A )
		{
			txt_buffer[i] = 0;
			if(i>0)
			{
				if(txt_buffer[i-1] = 0x0D)
					txt_buffer[i-1] = 0;
			}

			new_line_ptr[j++] = txt_buffer + i + 1;
		}
	}
 
Ipak neću da pokrivam nikakve slučajeve i duple newline oznake, jer će ulazni tekstualni fajlovi biti standardizovani, a prilično sam siguran da će se program koji pišem osim na Mac OS X-u izršavati samo još na Linux-u. ,)
 
Poslednja izmena:
Imam predmet u kome se "uči" C i radim zadatake za dve ocene više. Inače nakucao sam još 5 puta onoliko koda.

Dosta si mi pomogao primerom. Posle sam uradio učitavanja još dva fajla i izvlačenje po dva niza char pointera (sa 1 prema 1 korespodencijom) iz oba.

Zadatak je simuliranje prostog seta procesorskih instrukcija sa simboličkim adresama (kao imena promenljivih). Jedan tekst fajl je sekvenca instrukcija, drugi je tabela simboličkih adresa podataka, treći tabela simboličkih adresa instrikcija, a četvrti će biti početno stanje memorije. Najviše posla je bilo u pravilnoj obradi tekstualnih fajlova i dekodiranju instrukcijskih stringova u struct-ove. Samo još da ubacim kod za učitavanje pošetnog stanja memorije iz tekst fajla i mogu da testiram.

A onda dolaze još dva zadatka.
 
Ih, ides. Ja sam na II godini studija iz OOP (C++) imao zadatak troduplo laksi od toga na pismenom :D .
 
Ono "uči" sam stavio pod navodnike jer C na tom predmetu učimo od drugog dela semestra (u prvom delu smo učili Linux (konzola, vi)) i stigli smo tek do računanja proseka dva broja.

Tako da se podrazumeva da sam C već naučio van tog predmeta ako hoću da radim ove zadatke. Al krastavac tako je kod nas u obrazovanju. Predavanja Džerija Kejna sa Stanforda su zakon (i naravno ljudi sa ovog foruma :)).

Inače završio sam ovaj prvi zadatak od 3. Program učitava sa prompta veličinu memorije za instrukcije i veličinu memorije za podatke, zatim iz tekstualnih fajlova učitava listu instrukcija, pa tabelu simboličkih adresa podataka, pa tabelu simboličkih adresa instrukcija, pa početni sadržaj memrije (naravno sve to obrađuje i pravi odgovarajuće nizove char *). I onda dekodira instrukcije, praveći podatke korisnije od niza stringova. Na kraju sledi izvršavanje instrukcija i print sadržaja memrije. :D

Jeste li verovali da je jedan Mac-ovac u stanju da uradi ovo? :d:D:p,):)
 
Ja samo vidim da jedan Macovac ne ume da podeli tekst u redove. :D



E, ako rešavaš zadatke, ubaci proveru da li je fajl uspešno otvoren i da li je uspešno rezervisana memorija, da ti neko ne bi na tome oduzimao poene. Mislim to treba da ubaciš i ako ne rešavaš zadatke, ali dobro.... naučićeš vremenom da je error handling jedna dosadna stvar koja oduzima puno vremena (skoro kao i dokumentacija). :D

I gde vas to uče C, a ne C++?
 
Na FON-u. Iz predmeta Principi programiranja učimo Javu, a iz Arhitekture računara i operativnih sistema osim naslovnog gradiva radimo Linux i C. Biće još jedan programerski predmet u drugom semestru (Strukture podataka i algoritmi). Možda će tu biti C++.

Pa kako da znam kako se fajl učitava i deli na redove kad sam tek počeo oziljno da drljam C. :D Ali što je najvažnije brzo sam ukapirao i uspešno primenio znanje za osatak zadatka.

A za proveru grešaka, opušteno. Asistent je normalan IT-evac (razume se u šta god oćeš od računara) i prijatan čovek. Otićiću na konsultacije pa ću da ga pitam dal' treba.

A i zadaci su veoma jasno formulisani i obrazloženi. Razumem da je potrebno samo da program radi kako je zadato, nije važno na koji način dokle god ja razumem i mogu to da objasnim.
 
Poslednja izmena:
Moze i lakse uz upotrebu realloc. Treba ti jedan char** niz pointera na redove i jedan buffer za ucitavanje jedne linije. Iz ulaza citas slovo po slovo u buffer, dok ne napunis red i onda buffer "dodas" u redove. Ukoliko niz za redove nema mesta za novi red, uradi realloc tog niza (povecaj ga za 1 ili vise redova) i dodaj ga na prvu slobodnu poziciju.

Kod:
int main(int argc, char* argv[])
{
	char** redovi = NULL;
	char* red = NULL;
	int broj_redova = 0;
	int broj_slova = 0;
	int poz = 0;

	FILE *f = fopen(argv[1], "rt");
	if (f == NULL) return 0;
	
	while (!feof(f))
	{
		int c = fgetc(f);
		if (c=='\n' || feof(f)) /* da li je novi red ili kraj fajla */
		{	/* ako trenutni red nije prazan dodaj ga u redove */
			if (red!=NULL) 
			{
				red[poz] = 0;	/* ubaci 0 na kraj reda */
				broj_redova++;
				redovi = (char**)realloc(redovi, broj_redova*sizeof(char*));
				if (redovi == NULL) break;
				redovi[broj_redova-1] = red;
				/* priprema za sledeci red */
				red = NULL;
				broj_slova = 0;
				poz = 0;
			}
		}
		else
		{	/* dodaj slovo u red */
			if ((poz+1) >= broj_slova) /* +1 zato sto nam treba mesto za 0 na kraju stringa */
			{
				/* povecavamo bufer za red za 10 slova */
				broj_slova += 10;
				red = (char*)realloc(red, broj_slova);
				if (red == NULL) break;
				memset(red + poz, 0, 10);
			}
			red[poz++]=c;
		}
	}
	fclose(f);

	/* ispis */
	for (int i=0; i<broj_redova; i++)
		printf("\n%s", redovi[i]);

	/* oslobadjanje memorije */
	for (int i=0; i<broj_redova; i++)
		free(redovi[i]);
	free(redovi);
}
 
Poslednja izmena:
C++ verzija:
Kod:
#include <string>
#include <fstream>
#include <vector>

int main(int argc, char* argv[])
{
	std::vector<std::string> redovi;

	std::ifstream ifs( argv[2] );
	std::string red;

	while( getline( ifs, red ) ) redovi.push_back( red );
}
 
Zanimljivo...

Samo je pitanje šta je ekonomičnije. Učitavati ceo fajl odjednom i onda ga obrađivati; ili slovo po slovo i usput praviti redove i realocirati po potrebi. U svakom slučaju tvoj primer ima znatno više poziva za alokaciju memorije. (a kad se izbace nullovanja iz zub-ovog primera tvoj ima čak malo više koda)
 
Poslednja izmena:
Zavisi sta zelis da radis sa podacima na kraju. Ukoliko zelis da ih menjas (dodajes slova u red), u mom slucaju je to lakse izvesti. Ukoliko zelis samo da ih analiziras, onda je njegov primer bolji.

U mom primeru ima fragmentacije memorije, koju sam smanjio tako sto sam buffer za red povecavao za 10 mesta. Znaci, ako red ima 3 slova, bice alocirano 10 bajtova. Ako ima 93 slova bice alocirano 100 bajtova. Mogao sam i char** redovi da povecavam za 10 i jos vise smanjim fragmentaciju.

Na istom principu funkcionisu vector i string u STL biblioteci. Realokacija memorije je sasvim normalna stvar.

Pametni memory manageri odvajaju malo vise memorije nego sto se trazi bas zbog realokacije. Npr.. ako trazim 10 bajta on ce odvojiti 16. Ako trazim 20 on ce odvojiti 32 bajta, ako trazim 30, on nece nista da odvaja, vec ce samo postojeci da prosiri do 30 (jer ionako ima rezervisano 32). Ovaj princip se cesto koristi jer moderni procesori "vole" kada im se podaci nalaze na adresama koje su deljive sa 2, 4, 8, itd, jer lako upadaju u cache. Pametan memory manager ce voditi racuna da adrese alociranih blokova budu pogodne za procesor.
 
Ja nisam odavno koristio C, tako da mi je ostala navika da izbegavam realloc() koji je pre na Windows-u imao priličan problem sa performansama (ne znam kakvo je sada stanje). Mada i sada kada u drugim jezicima koristim već postojeće tipove podataka koji imaju implementirano realociranje memorije - trudim se da realokacije bude što manje. Što bi se reklo - stare navike teško umiru. :D

Inače pošto ti sve to pišeš na MacOSu slobodno koristi realloc(), pošto *nix sistemi imaju dosta bolji memory management i realloc() na njima radi dosta brže.
 
Poslednja izmena:
Idealno resenje bi bilo uvezana lista za redove i buffer za slova u redu. Potreban je jos jedan malo veci temp buffer u kome bi smestao red koji upravo citas. Ukoliko je temp buffer nedovoljan, realociraj ga ili napravi listu temp buffera u kome ces cuvati trenutni red. Kada napunis red, znas mu duzinu, alociraj jedan buffer, kopiraj red iz temp u buffer i dodaj buffer u listu redova, a zatim resetuj temp listu.

Mozes i da se igras i sa ftell i fseek.. ftell da upamtis gde je pocetak reda u fajlu, zatim citas slovo po slovo do kraja reda i usput brojis slova. Kada dodjes do kraja reda, znas koliko ima slova, alociras bufer za red, sa fseek se vratis na pocetak reda i onda jedan fread i ceo red je u bufferu.
 
Pišem ovaj post iz čiste sreće, jer program konačno radi kako treba, to jest uspeo sam da ga debuggujem do kraja i ispravim sve greške u kodu.

Woooohhooooooo, Oooooooo yeeeaaahhhh... :banana::party:


EDIT: Sad kad sam se smirio od sreće, da prokomentarišem.

Greške su uglavnom bile u stavljenoj 0 u proveru umesto 1 i ornuto, greške stavljanja = umesto == u proveru sa 0 (što prolazi kompajler bez problema iako je dodela u pitanju) i greške u copy-paste-u (kad nisam izmenio kako treba paste-ovani kod) što ne bi bilo da sam ga kucao iznova.

Eto tako. Pozdrav do sledećeg problema, ako ih bude sa druga dva zadatka.
 
Poslednja izmena:
(što prolazi kompajler bez problema iako je dodela u pitanju)

Kompajler ne prijavljuje grešku zato što je to sasvim validan izraz.

Ako napišeš:

Kod:
if ( a = b )
    do_something();

prvo će vrednost b dodeliti promenljivoj a, a onda će vrednost promenljive a biti korišćena za evaluaciju (0 - false, sve ostalo true). Operator jednako vraća vrednost isto kao i svi ostali operatori. Kao što a + b vrati (a+b), tako a = b vrati (b), samo što prvo a dobije istu vrednost kao b.


Znači možeš da napišeš ovako nešto:

Kod:
if (  (a=b) > (c=d=e-f) )
   do_something();
 
Poslednja izmena:
Jel znas koja je najgora greska koju C kompajler moze da prijavi?
You must be joking!? Right?

:)
 
Ajoj nismo se razumeli. :D

Mislio sam ironično. Znam ja zašto to prolazi kompajler (jer se ta dodela nule ukupno evaluira na vrednost promenljive kojoj se dodeljuje nula, a to je nula :p što je validan argument IF-u). Meni je ironija bila jer sam mislio da je osim nule validna još samo jedinica. Nisam znao da je i sve ostalo. To onda može da bude prednost jer omogužava razne zanimljive stvari (recimo dodela vrednosti zbira dve promenljive i provera da li je jednak nuli sve unutar zagrada IF-a), ali isto tako otvara prostor da se ljudima podkradu greške.

@yooyo: Hoćeš da kažeš da kompajler ne može da ispravlja moje logičke greške. Pa to ja znam, a i već sam objasnio u prethodnom pasusu šta sam mislio.

:wave:
 
To onda može da bude prednost jer omogužava razne zanimljive stvari

Zanimljive da, ali praktične - ne. :)

Ja sam se nekada trudio da iskoristim svaku prečicu koja postoji, pa sam onda na teži način shvatio da je mnogo važnije da kod bude lako čitljiv nego da ima manje redova. Uvek - readability over convenience.

Tako da sam odavno počeo da operacije uvek odvajam zagradama u neke logične celine čak i ako zbog asocijativnosti nije neophodno (pogotovu u SQL upitima), vitičaste zagrade stavljam svuda i naravno pišem komentare... i sve to dobrovoljno. :)

Dokumentaciju i dalje pišem samo kad moram. :D
 
Poslednja izmena:
Jedan moj bivsi kolega programer, je napisao sledecu konstrukcijuČ
Kod:
if (func1() == false && func2() == false)
{
 // prijavi gresku
}

Namera mu je bila da se OBE funkcije pozovu i da se onde testira da li je uspela neka akcija ili nije.
Kompajler je generisao code koji poziva func1 i ako ona vrati false, uopste se ne poziva func2. Gresku smo otkrili posle mnogo dana mucenja.
 
Pa to je vrlo logično. :D

Stavljeno je AND (&&) između. Prvo se poziva prva funkcija, kada ispadne False, automatski nema šanse da se ukupan uslov ispuni i nema potrebe pozivati drugu fukciju.

Da niste očekivali da vam kompajler prijavi: You must bi joking!? Right? :D

EDIT: Verovatno bi se isto desilo kad bi stavili OR (||) a prva funkcija vrat True. Nema potrebe da se zove druga.
 
Poslednja izmena:
Pa da.. ali ako neko hoce da pise u sto manje linija i ne razmislja o tome, nastane problem.
 
Treba mi informacija o fukciji strchr(). Kakav pointer ona tačno vraća ako ne nađe traženi karakter?

Ako je pointer jednak nuli, da li je dovoljno samo da ga tako ubacim u IF () i IF će registrovati False?
 
Vraca null ako ne nadje.

Null ti je isto sto i false, jer se null prevodi bukvalno kao nula. A if nece proci za nulu.

Inace:
http://www.cplusplus.com/

Da ne bi morao svaki put da cekas na odgovor ovde :) .
 
Poslednja izmena:
Nazad
Vrh Dno