Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: C-kysymyksiä

Sivun loppuun

msdos464 [18.07.2007 19:26:20]

#

Teinkin vielä yhden topickin, kun edellisessä oli vähän kehno otsikko näihin jatkokysymyksiin.

nyt pitäisi parsia päivämäärä, joka on esim. tällainen: 2006-05-08

Yritin kovasti löytää C:n split funktiota kunnes totesin, ettei sellaista taida löytyä. Koitin tehdä oman split() funktion käyttäen strtok funktiota (netistä löytyi esimerkki), mutta en osaa palauttaa funktiota. Tähän löytyi esimerkki jossa pelattiin pointtereiden kanssa, mutta nekään eivät oikein toimineet.

#import <stdlib.h>
#include <string.h>

int *parser(char str[]);

int main(void) {
    char *date1; /*, *date2;
    printf("Enter two dates:\n");
    scanf("%s", &date1);
    scanf("%s", &date2);*/

    date1 = "2000-06-10";

    int *num1 = parser(date1);

    printf("%s\n", num1[0]);

    free(num1);

    system("PAUSE");
    return 0;
}

int *parser(char str[]) {
    int *result = malloc(1000000), i;
    char *tmp;
    tmp = strtok(str, "-");
    for (i = 0; i < 2; i++)
      {
      result[i] = atoi(tmp);
      tmp = strtok(NULL, "-");
      }
    return result;
}

En tiedä paljonko tuon mallocin pitäisi varata muistia, niin heitin jonkun melko ison luvun siihen. Tuokin ohjelma kuitenkin kaatuu suoritettaessa :/ Saisiko apuja?

Ohhoh säännöt sanovat näin:

lainaus:

Publishing your own exercise code or part of it during the course is forbidden and is considered as cheating. Don't make your code available on www pages or post it to newsgroups.

Siinä tapauksessa tämä ei liitykkään koulun C kurssiin, vaan omaan harjoitteluuni ;)

Mazzimo [18.07.2007 20:59:22]

#

Jos haluat tehdä funktion alusta asti itse, käy merkkijono läpi merkki kerrallaan. Jos merkki on '-' (huomaa: ', ei "), varaat muistia merkkijonotaulukkoon (olkoon vaikka char temp[5]), johon tallennat merkit ennen tuota '-' merkkiä. Sitten käytät vaikka atoi -funktiota, jolla saat muutettua jonon numeroksi. Käyt tällä tavalla läpi kaikki tarvitsemasi tiedot.

msdos464 [18.07.2007 21:19:00]

#

Mutta en silti osaa palauttaa sitä taulukkoa (tai pointteria siihen).

Olisi tyhmää tehdä tuo koodi pätkä 2 kertaa peräkkäin siihen koodiin.. Kyllä tuon strok funktion käyttö onnistuu, kun otti esimerkistä mallia :)

Metabolix [18.07.2007 21:37:12]

#

Miksi keksiä pyörää uudestaan?

#include <stdio.h>
// ---
int y, m, d;
char pvm[] = "2006-07-18";
sscanf(pvm, "%d-%d-%d", &y, &m, &d);

Mikä tuo #import muuten yrittää olla? O_o Ja muistia sopii varata sizeof(int) jokaista alkiota kohden, siis kolme kertaa sizeof(int) tuossa. Ohjelmasi kaatuu, koska yrität printata lukua (%d) tekstin muodossa (%s), jolloin int tulkitaankin char*:ksi ja tulee virhe. Printtaa siis mainissa näin:

printf("%d %d %d\n", num1[0], num1[1], num1[2]);

Funktiosi määrittelyssä olisi kohteliasta olla const char* pvm, jotta kutsuja tietää, ettei tekstiä muuteta funktion aikana.

msdos464 [18.07.2007 21:40:46]

#

en tiennyt sscanf funktion olemassa oloa, ei tullut googlella vastaan. Tuohan helpottaa tehtävää paljon, kiitoksia :)

oho :D import lienee javaa, eihän se tuonne kuulu. Eiköhän tästä pääse taas eteenpäin, kysyttävää on viellä includettamisesta (mutta se huomenissa).

msdos464 [21.07.2007 11:23:59]

#

main.c

#include <stdlib.h>

#include "wallet.c"
#include "gamble.c"

int main(void) {
    Wallet w = 100.0;
    w = gabmle(w);
}

wallet.h

#ifndef WALLET_DEF
#define WALLET_DEF

typedef double Wallet;
void walletPrintBalance(Wallet w);
Wallet walletPay(Wallet w, double amount);

#endif

wallet.c

#include "wallet.h"

#include <stdio.h>
#include <stdlib.h>

void walletPrintBalance(Wallet w) {
	printf("You have %.2lf euros.\n", w);
}

Wallet walletPay(Wallet w, double amount) {
	/* koodia */
	return w;
}

gamlbe.h

Wallet gamble(Wallet w);

wallet.c

#include "gamble.h"
#include <stdio.h>

#include "wallet.c"
#include "util.c"

Wallet gamble(Wallet w) {
	/* koodia, käyttää util.c:n getPositiveValueta */
	return w;
}

util.h

#ifndef TKK_AS_C_UTIL_H
#define TKK_AS_C_UTIL_H

/** Read a positive value from stdin. Terminates the program on invalid input. **/
double getPositiveValue();

#endif

util.c

#include "util.h"

#include <stdio.h>
#include <stdlib.h>

double getPositiveValue() {
	double value;
	if (scanf("%lf", &value) != 1 || value <= 0.0) {
		puts("Invalid input!");
		exit(EXIT_FAILURE);
	}
	return value;
}

Tiedostot gamble.c, util.c, util.h ja wallet.c sain tehtävänannossa, tiedostot bank.c, bank.h, gamble.h, main.c ja wallet.h pitää palauttaa.

Tulee virhe "redefinition of 'walletPrintBalance'" (wallet.c rivillä 6) ja "redefinition of 'walletPay' (wallet.c rivillä 10). Tämä johtuu ilmeisesti siitä, että wallet.c includetetaan sekä main.c:ssä että gambel.c:ssä. Koitin laittaa niitä ehtolauseta enemmänkin, jolloin linkkeri sanoi ettei löydy jotain funktiota tjsp. Miten tämä nyt pitäisi tehdä?

kooderi [21.07.2007 12:29:54]

#

Pitäis incluudata .h -tiedostot eikä .c:tä.

Metabolix [21.07.2007 12:35:40]

#

Jos kerran koulutehtäviin kysyy apua, niin voisi edes yrittää olla typottelematta ja tarkistaa, että nimesi tiedostot oikein. Tuolla on wallet.c kahteen kertaan ja gamble kirjoitettu milloin mitenkin.

msdos464 kirjoitti:

Tämä johtuu ilmeisesti siitä, että wallet.c includetetaan sekä main.c:ssä että gambel.c:ssä. Koitin laittaa niitä ehtolauseta enemmänkin, jolloin linkkeri sanoi ettei löydy jotain funktiota tjsp.

Oikea virhe, väärä ratkaisu. Miksi pitää ajatella noin vaikeasti? Ota toinen include pois, niin ei enää ole kuin yhdessä paikassa.

msdos464 [21.07.2007 12:53:56]

#

Oho.. alempi wallet.c pitäisi olla gamble.c.

Ei tuo kyllä toimi jos main.c:stä includetan ainoastaan .h tiedostot.. Ne .c tiedostothan includettavat aluksi ne omat .h tiedostot. Sain ohjelman nyt korjattua.

Siksi se include oli kahdessa paikassa, kun luulin, että gamble.c:ssä pitäisi myös kertoa, että millainen se wallet typedef on.

edit: lisää ongelmia.

Omalla koneella tuo toimii niinkuin pitääkin, mutta kun lähetän sen koulun koneen scriptille tarkastettavaksi, se ei saa käännettyä / ajettua sitä.

lainaus:

In file included from bank.c:1:¶
bank.h:1: error: syntax error before "bankVisit"¶
bank.h:1: error: syntax error before "w"¶
bank.h:1: warning: type defaults to `int' in declaration of `bankVisit'¶
bank.h:1: error: ISO C forbids data definition with no type or storage class¶
bank.c:6: error: syntax error before "bankVisit"¶
bank.c:6: error: syntax error before "w"¶
bank.c:6: warning: return type defaults to `int'¶
bank.c: In function `bankVisit':¶
bank.c:8: warning: implicit declaration of function `printf'¶
bank.c:9: warning: implicit declaration of function `getPositiveValue'¶
bank.c:12: error: `w' undeclared (first use in this function)¶
bank.c:12: error: (Each undeclared identifier is reported only once¶
bank.c:12: error: for each function it appears in.)¶
bank.c:12: warning: implicit declaration of function `walletPay'¶
bank.c:7: warning: unused variable `temp'¶
In file included from gamble.c:1:¶
gamble.h:1: error: syntax error before "gamble"¶
gamble.h:1: error: syntax error before "w"¶
gamble.h:1: warning: type defaults to `int' in declaration of `gamble'¶
gamble.h:1: error: ISO C forbids data definition with no type or storage class¶
gamble.c:7: error: conflicting types for 'gamble'¶
gamble.h:1: error: previous declaration of 'gamble' was here¶
gamble.c:7: error: conflicting types for 'gamble'¶
gamble.h:1: error: previous declaration of 'gamble' was here¶
In file included from main.c:5:¶
bank.c: In function `bankVisit':¶
bank.c:7: warning: unused variable `temp'¶

main.c includettaa wallet.c:n, joka includettaa wallet.h:n, jossa määritellään tuo "typedef double Wallet;"

main.c includettaa myös bank.c:n, jonka includettamassa bank.h:ssa on "Wallet bankVisit(Wallet w);", josta tuo ei näemmä ymmärrä, että mitä tuo Wallet tarkoittaa.

Omalla koneellani (Win, Dev-C++ 4.9.9.2) tuo siis toimii. Koulun kone ajoi tuon komennolla

/usr/bin/gcc -std=c99 -pedantic -Wall -Wextra -g -lm -o esko bank.c gamble.c main.c util.c wallet.c

Sääntöjen mukaan en vissiin saisi pastea koko omaa koodiani tänne, ettei joku sitten lunttaa siitä. :P Pitäisikö yrittää includettaa wallet.c:tä muistakin tiedostoista? Silloinhan tulee ongelmia kun joitakin asioita on määritelty ja esitelty jo aiemmin.

Metabolix [21.07.2007 13:23:37]

#

Jos koulun kone kääntää sen noin, niin c-tiedostojen includen kanssa se ei varmasti suostu linkkautumaan. O_o

Koska otsikoissa on tuo ehtosysteemi, ne voi huoletta includettaa minne vain, ja niissä kuuluu olla kaikki sellaiset määrittelyt, joita muut tiedostot voivat tarvita. C-tiedostoja ei kuulu liittää includella, kun ne käännetään itsenäisesti (kuten tuossa käännöskomennossa näkyy).

Tee siis näin: Poista kaikki includet, joissa liität c-tiedoston. Lisää kaikkiin h-tiedostoihin tuollainen ifndef-systeemi, kuin joissakin jo on. Lisää sitten kaikki ne h-tiedostojen includet, joita tarvitaan, jotta oikeat asiat on määritelty oikeassa paikassa. Jos et osaa muuten ajatella noita, niin liitä vaikka kaikki, ei niistä suoranaista haittaa ole.

msdos464 [21.07.2007 13:39:34]

#

Tein niin kuin käskit.. main.c:ssä includetin wallet.h, bank.h ja gamble.h:n.
Toinen .c tiedosto mikä pitää palauttaa on bank.c, sieltä includetan ainoastaan bank.h:n. .h tiedostoista ei ole mitään includeja, ainoastaan omat ifndef hommat ympäröivät koko koodin.

Oma kääntäjä sanoo näin:

lainaus:

[Linker error] undefined reference to `walletPrintBalance'
[Linker error] undefined reference to `bankVisit'
[Linker error] undefined reference to `gamble'
[Linker error] undefined reference to `walletPrintBalane'
ld returned 1 exit status

Koulun kone sanoo näin:

lainaus:

In file included from bank.c:1:¶
bank.h:3: error: syntax error before "bankVisit"¶
bank.h:3: error: syntax error before "w"¶
bank.h:3: warning: type defaults to `int' in declaration of `bankVisit'¶
bank.h:3: error: ISO C forbids data definition with no type or storage class¶
bank.c:6: error: syntax error before "bankVisit"¶
bank.c:6: error: syntax error before "w"¶
bank.c:6: warning: return type defaults to `int'¶
bank.c: In function `bankVisit':¶
bank.c:8: warning: implicit declaration of function `printf'¶
bank.c:9: warning: implicit declaration of function `getPositiveValue'¶
bank.c:12: error: `w' undeclared (first use in this function)¶
bank.c:12: error: (Each undeclared identifier is reported only once¶
bank.c:12: error: for each function it appears in.)¶
bank.c:12: warning: implicit declaration of function `walletPay'¶
bank.c:7: warning: unused variable `temp'¶
In file included from gamble.c:1:¶
gamble.h:3: error: syntax error before "gamble"¶
gamble.h:3: error: syntax error before "w"¶
gamble.h:3: warning: type defaults to `int' in declaration of `gamble'¶
gamble.h:3: error: ISO C forbids data definition with no type or storage class¶
gamble.c:7: error: conflicting types for 'gamble'¶
gamble.h:3: error: previous declaration of 'gamble' was here¶
gamble.c:7: error: conflicting types for 'gamble'¶
gamble.h:3: error: previous declaration of 'gamble' was here¶
main.c: In function `main':¶
main.c:12: warning: implicit declaration of function `printf'¶
main.c:13: warning: implicit declaration of function `scanf'¶

Kun includetan ainoastaan .h tiedostoja, mistä se tietää katosa infoa myös .c tiedostoista?

Metabolix [21.07.2007 13:52:43]

#

Oma linkkerisi sanoo noin, koska et käännä kaikkia tiedostoja. Dev-C++:n kanssa tuo siis tarkoittaa, ettet ole lisännyt niitä projektiin. Kääntäjälle riittää h-tiedostossa oleva tieto, että on olemassa funktio x, jolle kelpaa parametri y. Kääntäjä luo c-tiedostosta objektitiedoston (.o), jossa funktiokutsussa kerrotaan vain, että tässäpä kutsutaan tuollaista funktiota. Linkkeri sitten yhdistää nämä o-tiedostot ohjelmaksi ja hoitaa, että nuo funktiokutsut muutetaan hypyiksi oikeaan kohtaan jotain toista objektia. Näin yksinkertaistettuna mallina.

Muistahan, että esimerkiksi wallet.h täytyy liittää myös gamble.h:n alussa (ts. heti ifndef-säädön jälkeen), jotta sielläkin ollaan perillä, mikä ihme tuo Wallet-tyyppi on. Ja näin vastaavasti kaikki muutkin tarpeelliset. Voisit myös itse opetella lukemaan noita virheilmoituksia; esimerkiksi nuo kaksi ensimmäistä kertovat selvästi, että kääntäjä ei tunne sanaa Wallet, ja ratkaisu on ilmeinen: liitetään edellisellä rivillä se tiedosto, jossa kerrotaan, mikä se sellainen Wallet on.

Nuo ilmoitukset printf:stä sun muista tarkoittavat, että et ole kyseisen tiedoston alussa liittänyt stdio.h:ta.

Yritäpä tehdä käännösprosessia vähän ihan käsin tekstieditorilla, katsele, mitä tulee aina kunkin includen jälkeen koodin sisällöksi, niin ymmärrät, mikä mättää: toisistaan riippuvat asiat tulevat määritellyiksi väärässä järjestyksessä, Walletia käyttävä funktio ei vielä tiedä, mikä se edes on.

msdos464 [21.07.2007 14:34:26]

#

Tuolla aiemmin sanoit näin:

lainaus:

Ota toinen include pois, niin ei enää ole kuin yhdessä paikassa.

Ajattelin sitten, ettei sen tarvitse olla kuin yhdessä paikassa. En myöskään tiennyt, että kannattaisi pitää yhden kokonaisuuden tiedostot yhdessä projektissa.. (tai siis että siitä olisi jotain haittaa, jos näin ei tee)

Noniin, nyt sain täydet pisteet (ei edes varoituksia :o)

Seuraava ongelma: input.txt sisältää tällaista dataa:

12
3131
4242
123

Eli siis kokonaislukuja omilla riveillään. Varsinaisessa tehtävässä inputti näyttää tältä:

465566339:9
12345: 12
523 : -12
  	241251	: 5
3201234123: 7
0: 13
53452: -7
	322123:2
 1222702060 : 12

Tehtävänannossa neuvottiin käyttämään fscanf funkkaria. Yritin lukea tuota omaani input.txt:tä (koska se on yksinkertaisempi) näin:

#include <stdlib.h>
#include <stdio.h>

int main(void);

int grades() {
    FILE *infile;
    if((infile = fopen("input.txt", "r")) == NULL)
      return -1;

    int tmpId;
    system("PAUSE");
    while(fscanf(infile, "%i", tmpId) == 1) {
        system("PAUSE");
        printf("%i\n", tmpId);
    }
    fclose(infile);
    return 0;
}

int main(void) {
    int tmp = grades();
    system("PAUSE");
    return 0;
}

Ohjelma kaatuu ensimmäisen PAUSE:n jälkeen, eli ilmeisesti tuolla while(fscanf...) rivillä. En löytänyt googlella kokonaista fscanf esimerkkiä, jota olisin voinut kokeilla.

msdos464 [21.07.2007 14:42:34]

#

Juu mutta yritän ensiksi saada luettua omaani inputtia.

os [21.07.2007 14:49:39]

#

fscanf:lle pitää syöttää osoite eli
fscanf(infile, "%i", &tmpId).

Edit: kauheaa sekoilua taas näiden edittien kanssa. Pahoittelen.

msdos464 [21.07.2007 15:11:01]

#

Oho, heti rupesi toimimaan :o

luulin kokeilleeni tuota, mutta taisinkin laittaa sen & merkin tuohon alempaan printf funkkariin.

Kun tuola tehtävänannon mukaisessa input.txt:ssä on noita välilyöntejä ja muuta sellaista seassa, millaisella syntaksilla ne pitäisi hoidella? reqular expressionilla tuo onnistuisi helposti.. En löytänyt kovin hyvää tutorialia / esimerkkiä tuon muotoilun syntaksista.

while(fscanf(infile, "%li:%i", &tmpId, &tmpPoints) == 2) löytää ainoastaan ne, missä ei ole white spaceja. Yksi ratkaisu, mikä tuli mieleen, olisi poistaa kaikki merkit jotka eivät ole numeroita, - merkki tai kaksoispise. Mutta kyllä tuo varmaan jotenkin muutenkin onnistuu.

Metabolix [21.07.2007 15:24:32]

#

"%d : %d", koska väli skippaa kaikki whitespacet (nolla tai enemmän). C:n speksi kertoo tarkemmin, sieltä voi aina katsella funktioiden kuvauksia ja toimintaa. Suosittelen. %d on turvallisempi kuin %i, koska %i voi osata tulkita esimerkiksi 011:n oktaalilukuna ja 0x1f:n heksana... Periaatteessa.

msdos464 [21.07.2007 15:32:55]

#

Hienoa, kiitos paljon. Koska tuossa kurssissa on ainoastaan 6 tehtävä kierrosta (noin 30 tehtävää), joista olen yhden jo tehnyt, en haluaisi ostaa C kirjaa, kun ne kirjat tuppaa jäämään lojumaan jonnekkin hyllyyn (on kätevämpi etsiä googlella). Tietty kirjasto olisi yksi hyvä vaihtoehto.

Tuo lukee luvun 3201234123 negatiiviseks (-1093733173).. ilmeisesti siksi, että meni lukualueesta yli.

unsigned int tmpId;
int tmpPoints, row = 0;
while(fscanf(infile, "%ld : %d", &tmpId, &tmpPoints) == 2) {
    printf("%d => %ld, %d\n", ++row, tmpId, tmpPoints);
}

Eikö tuon pitäisi kuitenkin käsitellä se ihan oikein?

Legu [21.07.2007 15:49:46]

#

Ei. %d tarkoittaa etumerkillistä kokonaislukua. Käytä tuohon ensimmäiseen "kenttään" (jossa ei näemmä negatiivisia ollut ollenkaan) %u:ta joka on etumerkitön kokonaisluku. Tällöin sen pitäisi näkyä oikein. (lue sitä manuaalia, esim. täältä löytyy nuo)

msdos464 [21.07.2007 17:32:04]

#

void scan(unsigned int* count, double* last, double* sum) {
     double tmp, tmp2 = 0;
     unsigned int i;
     for (i = 0; i < *count; i++) {
         scanf("%lf", &tmp);
         printf("%lf\n", tmp);
         if (sum != NULL)
             tmp2 += tmp;
     }
     /* ... */
}

Pitäisi käsitellä myös virheellinen syöte.. mitä scanf palauttaa, jos se ei onnistu lukemaan annettua syötettä? Ohjelman toiminta vaikutti aika oudolta siinä tapauksessa. Pitäisikö ensiksi lukea stringi, josta sitten kokeilla muuttaa doubleksi?

Blaze [21.07.2007 17:41:28]

#

http://www.cppreference.com/stdio/scanf.html

msdos464 [21.07.2007 17:55:44]

#

Kai tuo tuli googlella vastaan, mutta skippasin ne sivut missä puhuttiin C++:asta. Täytyy jatkossa tuolta katsoa nuo dokumentaatiot.

Metabolix [21.07.2007 19:35:38]

#

msdos464 kirjoitti:

– – En haluaisi ostaa C kirjaa, kun ne kirjat tuppaa jäämään lojumaan jonnekkin hyllyyn (on kätevämpi etsiä googlella).

Kuka tässä on kirjasta puhunut? C:n ja C++:n speksit ovat PDF:nä nätisti jaossa ihan ilmaiseksi.

Tuosta lukemisesta vielä, niin luvun meneminen lukualueen yli riippuu lipun lisäksi myös muuttujatyypistä: jos se menee %d-lipulla lukualueen yli, niin ei se myöskään int-muuttujaan mahdu, tarvitset unsigned int -muuttujan (tai jotain suurempaa ja vastaavan laajemman lipun).

os [22.07.2007 13:10:36]

#

msdos464 kirjoitti:

Pitäisi käsitellä myös virheellinen syöte.. mitä scanf palauttaa, jos se ei onnistu lukemaan annettua syötettä? Ohjelman toiminta vaikutti aika oudolta siinä tapauksessa. Pitäisikö ensiksi lukea stringi, josta sitten kokeilla muuttaa doubleksi?

Pitää. Ohjelma kaatuu, jos scanf:lle antaa virheellisen syötteen (kirjaimia numeroiden paikalla). Syötteen tarkistamisen jälkeen tallennettua merkkijonoa voi turvallisesti lukea vaikka sscanf:llä tai atof / strtod -funktioilla.

KeKimmo [22.07.2007 13:19:43]

#

scanf palauttaa "osumien" määrän, eli esim.

scanf("%d", &luku);

palauttaa syötteenä kokonaisluvun saadessaan numeron 1. Mikäli syötteestä ei saa järkevästi numeroa, se palauttaa 0. Mikäli luettava virta/tiedosto loppuu kesken tai tapahtuu virhe, scanf palauttaa EOFin.

EDIT: Esimerkki:

scanf.c:

#include <stdio.h>


int main (int argc, char* argv[])
{
        int luku, palautusarvo;

        printf("Anna luku: ");

        palautusarvo = scanf("%d", &luku);

        printf("Palautusarvo on %d.\n", palautusarvo);

        return 0;
}
kimmo@vyne ~ $ gcc scanf.c -o scanf -ansi -pedantic -Wall -Werror
kimmo@vyne ~ $ ./scanf
Anna luku: 5
Palautusarvo on 1.
kimmo@vyne ~ $ ./scanf
Anna luku: moi
Palautusarvo on 0.

Metabolix [22.07.2007 16:38:06]

#

os kirjoitti:

msdos464 kirjoitti:

Pitäisikö ensiksi lukea stringi, josta sitten kokeilla muuttaa doubleksi?

Pitää. Ohjelma kaatuu, jos scanf:lle antaa virheellisen syötteen (kirjaimia numeroiden paikalla).

Kuten KeKimmo yllä kertoo, tämä ei tosiaan pidä paikkaansa. KeKimmon esimerkkiin lisäisin vielä seuraavaa: Kun palautusarvosta on todettu, että scanf epäonnistui, puskuri sisältää yhä nuo vialliset merkit. Siis jos kirjaimia yrittää muuttaa luvuksi ja tulee virhe, kirjaimet jäävät yhä odottamaan lukemista. Seuraavan sanan saa näppärästi poistettua puskurista lukemalla formaatilla %*s, missä siis s merkitsee sanaa ja * tarkoittaa, että tulosta ei tallenneta minnekään.

int lue_luku()
{
  int i, onnistui = 0;
  while (!onnistui) {
    printf("Anna luku: ");
    if (scanf("%d", &i) == 1) {
      onnistui = 1;
    } else {
      printf("Syöttämäsi teksti ei kelpaa!\n");
      scanf("%*s");
    }
  }
  return i;
}

msdos464 [26.07.2007 20:50:52]

#

Seuraava ongelma, muistivuotoja :P

#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>

double eval(char **input);
int numeric(char *str);

int main(int argc, char *argv[]) {
    if (argc < 2) {
         printf("Usage: %s expression\n", argv[0]);
         exit(EXIT_FAILURE);
         }
    char **input = (char**) malloc(sizeof(char*)*argc);
    int i;
    for (i = 0; i < argc-1; i++) {
        input[i] = malloc(sizeof(char)*strlen(argv[i+1]));
        strcpy(input[i], argv[i+1]);
    }
    input[i] = NULL;
    printf("%lf\n", eval(input));

    for (i = 0; i < argc; i++)
        free(input[i]);
    return 0;
}

double eval(char **input) {
    /* ... */
    return eval(input);
}

/* ... */

Laskee siis parametreinä annettavan laskutoimituksen (ei sisällä sulkuja, ainoastaan + - / *)..

Oikealla syötteellä ohjelma laskee oikein, mutta koulun serverin valgrind valittelee, että musitia hukkuu =P Todennäköisesti yritän vapauttaa muistia väärin tuossa main funktiossa. Itselläni ei (tietääkseni) ole käytössä tuollaista debuggeria (Dev-cpp 4.9.9.2), ja kouluun on noita palautuskertoja erittäin rajallinen määrä. Mikä siis avuksi?

edit: ainiin, tarkkuudesta.

Suoritus etenee näin: (hakasten sisällä on taulukon input arvot eri indekseissä)

eval.exe -3.48 "*" 8.25 / 9.54 "*" 2.31 "*" -5.48 + -3.81 "*" -4.28 / -7.92 - -4.91 + -5.18 "*" -6.66 / -5.47
[-3.48] [*] [8.25] [/] [9.54] [*] [2.31] [*] [-5.48] [+] [-3.81] [*] [-4.28] [/] [-7.92] [-] [-4.91] [+] [-5.18] [*] [-6.66] [/] [-5.47]
[-28.710000] [/] [9.54] [*] [2.31] [*] [-5.48] [+] [-3.81] [*] [-4.28] [/] [-7.92] [-] [-4.91] [+] [-5.18] [*] [-6.66] [/] [-5.47]
[-3.009434] [*] [2.31] [*] [-5.48] [+] [-3.81] [*] [-4.28] [/] [-7.92] [-] [-4.91] [+] [-5.18] [*] [-6.66] [/] [-5.47]
[-6.951793] [*] [-5.48] [+] [-3.81] [*] [-4.28] [/] [-7.92] [-] [-4.91] [+] [-5.18] [*] [-6.66] [/] [-5.47]
[38.095826] [+] [-3.81] [*] [-4.28] [/] [-7.92] [-] [-4.91] [+] [-5.18] [*] [-6.66] [/] [-5.47]
[38.095826] [+] [16.306800] [/] [-7.92] [-] [-4.91] [+] [-5.18] [*] [-6.66] [/] [-5.47]
[38.095826] [+] [-2.058939] [-] [-4.91] [+] [-5.18] [*] [-6.66] [/] [-5.47]
[38.095826] [+] [-2.058939] [-] [-4.91] [+] [34.498800] [/] [-5.47]
[38.095826] [+] [-2.058939] [-] [-4.91] [+] [-6.306910]
[36.036887] [-] [-4.91] [+] [-6.306910]
[40.946887] [+] [-6.306910]
[34.639977]
34.639977

Oikea tulos olisi kuitenkin (noin) 34.639973 (viimeinen numero eri). Miten tarkkuutta voisi parantaa? Koodissa ilmenee mm. tällaista:

double a = atof(input[runInd-1]), b = atof(input[runInd+1]), c;
switch (input[runInd][0]) {
       case '*':
            c = a * b;
            break;
       case '/':
            c = a / b;
            break;
       case '+':
            c = a + b;
            break;
       case '-':
            c = a - b;
            break;
       default:
            exit(EXIT_FAILURE);
    }

    sprintf(input[runInd-1], "%lf", c);

indeksissä runInd on jokin operaattori ( +-/* ). Voisiko tuota jotenkin saada tarkemmaksi?

Metabolix [26.07.2007 21:31:59]

#

Tarkkuutta varten voit toki kokeilla long double -tyyppiä ja luvun lukemisessa vastaavasti strtold-funktiota. Tuosta tarkemmaksi sitten tarvitset jotain asiaan erikoistunutta, tuskinpa kannattaa.

Muistista: teit hienon silmukan noiden muiden vapautukseen (vaikka yrität kyllä vapauttaa myös lopusta NULL-osoittimen), mutta unohdit nyt sen ensimmäisen varaamasi palan. ^^ Yksi debug-tapa olisi myös korvata malloc ja free omilla vastaavilla funktioilla (tai makroilla), jotka kutsuisivat noita oikeita ja siinä sivussa printtaisivat kyseisen osoittimen, jolloin voisit tuossa todeta, että ensiksi printattu arvo ei enää ilmaannu uudestaan vapautusvaiheessa.

msdos464 [26.07.2007 21:53:14]

#

Pitää kokeilla tuota long doublea, en tarvitsisi kuin 2 desimaalia lisää.

lainaus:

teit hienon silmukan noiden muiden vapautukseen (vaikka yrität kyllä vapauttaa myös lopusta NULL-osoittimen), mutta unohdit nyt sen ensimmäisen varaamasi palan

eli pitää vielä loppuun tehdä free(input); ? toisessa ohjelmassa kokeilin tuota, muuta siinä tuli joku outo virhe ja sain 0 pistettä takaisin :P

"Your program crashed, or was killed by the operating system!"

No ehkä siinä kusi sitten jokin toinen asia..

Tuohon tämän hetkiseen koodiin valgrind ilmoitti 217 virhettä ja 30 muistivuotoa :S Kuulostaa aika paljolta... eval() fuktiosta vuodot voisivat "kumuloitua", mutta siitä ei kuitenkaan taida olla kyse? No, tuosta on vielä 8 palautuskertaa jäljellä.

lainaus:

Yksi debug-tapa olisi myös korvata malloc ja free omilla vastaavilla funktioilla (tai makroilla), jotka kutsuisivat noita oikeita ja siinä sivussa printtaisivat kyseisen osoittimen, jolloin voisit tuossa todeta, että ensiksi printattu arvo ei enää ilmaannu uudestaan vapautusvaiheessa.

Ööh... Mitä tarkoittaa "ensiksi printattu arvo ei enää ilmaannu uudestaan vapautusvaiheessa", sitä että muuttujalla olisi eri muistiosoite?

Metabolix [26.07.2007 22:06:41]

#

msdos464 kirjoitti:

Mitä tarkoittaa "ensiksi printattu arvo ei enää ilmaannu uudestaan vapautusvaiheessa", sitä että muuttujalla olisi eri muistiosoite?

Ei, vaan että näkisit, että ensimmäinen osoite, jonka malloc palauttaa, ei päädy koskaan free-funktiolle. Eli muistahan vapauttaa se input, mielellään vasta sen silmukkasi jälkeen. ;)

Lisäksi sinulla on sellainen pieni bugi, että et varaa tilaa joka rivin loppuun kuuluvalle nollatavulle. Siinä mallocissa pitäisi siis varata vielä yksi ylimääräinen tavu joka riville. Sen lisäksi saat tuhoa ja hävitystä aikaan, kun sprintf:llä tunget uuden arvon aina edellisen päälle. Eihän se välttämättä mahdu! Tuossakin luku on monta numeroa pidempi.

Oikea tapa olisi lukea aluksi kaikki luvut taulukolliseen liukulukumuuttujia ja hoitaa homma niillä loppuun asti. Muistiongelmat ratkeaisivat paljon helpommin, eikä tarkkuus kärsisi luvun pyöristelystä jatkuvan lukemisen ja kirjoittamisen kanssa.

msdos464 [27.07.2007 14:59:41]

#

Sain nyt 700 / 1000 pistettä, pisteitä lähti taas muistivuodoista :/ Tarkkuuden sain paremmaksi näin:

sprintf(input[runInd-1], "%.15lf", c);

Millä minä nuo muistivuodot nyt löytäisin? Olisihan se aika outoa, että linuxille on saatavilla ilmaiseksi avoimella lähdekoodilla softa, jota ei ole portattu windowsille (eikä vastaavaa ilmaista ole?). Kun tuosta sain yli pisteistä, on palautukseni hyväksytty, mutta kyllä kuitenkin pitäisi oppia pitämään muisti kasassa..

Metabolix [27.07.2007 15:23:53]

#

msdos464 kirjoitti:

Olisihan se aika outoa, että linuxille on saatavilla ilmaiseksi avoimella lähdekoodilla softa, jota ei ole portattu windowsille (eikä vastaavaa ilmaista ole?).

Mikä siinä on niin outoa? Useimmilla Linux-ohjelmilla asia taitaa olla alun perin juuri näin. ^^

Jospa noudattaisit loppujakin ohjeita ja luopuisit tuosta sprintf:stä. Nykyisellään teet jokseenkin näin:

char luku1[3] = "12";
char luku2[3] = "11";
// Varaat noille sen kolme tavua, etkö vain?
// ...
double c = 12.0 / 11.0;
sprintf(luku1, "%.15f", c); // Tulostaa 1.090909090909091

Taisi mennä vähän yli siitä kolmesta tavusta tai edes niistä yhteensä kahdeksasta, jotka kahdelle luvulle ja niiden väliselle merkille on yhteensä varattu. Muuta luvut jo alussa liukuluvuiksi, niin pääset tästä bugista!

msdos464 [27.07.2007 15:30:50]

#

lainaus:

Muuta luvut jo alussa liukuluvuiksi, niin pääset tästä bugista!

Sitten täytyisi tehdä uusi funkkari, jolla parsin operaatio merkit yhteen taulukkooon ja luvut toiseen.. Samalla pitäisi muuttaa koko eval() funkkari käyttämään kahta taulukkoa. Kyllähän se niinkin onnistuisi (ei sillä, ettenkö osaisi), mutta ohjelma toimii noinkin. Täytyy seuraavissa tehtävissä miettiä vähän tarkemmin, että miten se kannattaisi toteuttaa.

Muutin sitäpaitsi muistinvarausta tällaiseksi:

for (i = 0; i < argc-1; i++) {
        input[i] = malloc(sizeof(char)*50);
        strcpy(input[i], argv[i+1]);
    }

Käyttää hieman enemmän muistia kuin on välttämätöntä, mutta ei se nykykoneilla ole niin justiinsa (ainakaan tämän kokoluokan "projekteissa").

Metabolix [27.07.2007 16:12:43]

#

Sitten jos tuon pitäisi vapauttaa muisti myös virhetilanteessa, voist vaihtaa input-muuttujan globaaliksi, siirtää vapautuksen omaan funktioon void vapauta(void) ja kutsua main-funktion alussa stdlib.h:sta löytyvää funktiota atexit parametrilla vapauta, jolloin tuo funktio ajetaan ohjelman lopussa automaattisesti.

#include <stdlib.h>

char **input = 0;
int lkm = 0;

void vapauta(void)
{
  int i;
  if (input) {
    for (i = 0; i < lkm; ++i) {
      if (input[i]) {
        free(input[i]);
      }
    }
    free(input);
  }
}

int main()
{
  int i;
  atexit(vapauta);
  lkm = 10;
  input = malloc(sizeof(char*) * lkm);
  if (input == NULL) {
    exit(EXIT_FAILURE);
  }
  for (i = 0; i < lkm; ++i) {
    input[i] = malloc(sizeof(char) * (i+1));
    if (input[i] == NULL) {
      exit(EXIT_FAILURE);
    }
  }
  return 0;
}

msdos464 [28.07.2007 13:53:25]

#

Seuraava tehtävä, ajatus vähän jumittaa.. Tehtävänä on löytää "polku" eräänlaisesta pointteri sokkelosta.

http://msdos464.no-ip.com/diagram.png

Funktiolle annetaan syötteenä (käsittääkseni) 2 muistiosoitetta, tehtävänä on löytää reitti välille begin => end. Funktio palauttaa size_t* taulukon, josta tulee ilmi ratkaisu. Tuossa esimerkissä se olisi {0, 2}. Jos ratkaisua ei löydy, funktio palauttaa NULL.

Miten voin tarkastaa, että viittaako tietty pointteri NULL alkioon? Yritin aluksi jotain tällaista:

for (i = 0; ((int)begin)+i != NULL; i++)
    printf("%i\n", i);

Tuohan ei tietenkään toimi, koska ei tuosta yhteenlaskusta tule koskaan NULL:ia.

&begin taitaa kertoa, että mistä osoitteesta seuraavan taulukon alku löytyy?

Tämäkin antoi vähän outoa tulostusta:

#include <stdlib.h>
#include <stdio.h>
#include "lost.h";

size_t* solve(void* begin, void* end) {
     unsigned int i;
     for (i = 0; *(&begin+i) != NULL; i++)
         printf("%i\n", i);

     return NULL;
}

Tuo tulosti luvut 0 - 29, kun lostmain.c on tällainen:

/* ... */

int main(void) {
	// Creating a maze
	void** a = (void**) malloc(sizeof(void*));
	void** b = (void**) malloc(4 * sizeof(void*));
	void** c = (void**) malloc(2 * sizeof(void*));
	void** d = (void**) malloc(4 * sizeof(void*));
	void** e = (void**) malloc(2 * sizeof(void*));
	void** f = (void**) malloc(sizeof(void*));
	a[0] = b[3] = c[1] = d[3] = e[1] = f[0] = NULL;
	b[0] = d; b[1] = a; b[2] = f;
	c[0] = b;
	d[0] = e; d[1] = c; d[2] = f;
	e[0] = d;
	size_t* solution = solve(c, f);
	if (!solution) {
		puts("No solution");
	} else {
		for (size_t i = 1; i <= solution[0]; ++i) printf("->%zu", solution[i]);
		putchar('\n');
	}
	/* ... */
	system("PAUSE");
}

Eihän tuossa pitäisi noin montaa lukua tulostua, kun c[0] viittaa e:hen, jonka pituus on vain 2.

os [28.07.2007 16:29:45]

#

Mikäli ymmärsin oikein, niin tehtävä liittyy hakuun osoittimien muodostamasta verkosta:
http://en.wikipedia.org/wiki/Breadth-first_search
http://en.wikipedia.org/wiki/Depth-first_search

Ongelmasi on nyt se, ettet itse asiassa seuraa pointtereiden muodostama polkua mihinkään. Sinun pitäisi kutsua solve-funktiota rekursiivisesti jokaisen ei-NULL-arvoisen osoittimen kohdalla tämän osoittamalle kohdalle, ja tulostaa polku vasta jälkikäteen, kun oikea reitti löytyy.

int solve(void** begin, void* end) {
   unsigned int i;
    for (i = 0; begin[i] != NULL; i++)
      if(begin[i]==end || solve((void**)begin[i],end)) {
         /* tallenetaan löydetyn polun solmu (i) esim. pinoon */
         return 1;
      }
   return 0; // polkua ei löytynyt
}

msdos464 [28.07.2007 16:44:04]

#

lainaus:

Ongelmasi on nyt se, ettet itse asiassa seuraa pointtereiden muodostama polkua mihinkään.

Tiedän, en tässä vaiheessa ole vielä toteuttanut sitä, kun en osannut edes seurata noita pointtereita. Yritin ensiksi selvittää, että montako alkiota on siinä taulukossa, johon begin viittaa ensimmäisellä kutsukerralla. Oikea vastaus olisi tässä tapauksessa siis 2. (1 viittaus ja 1 NULL).

Tuossa koodissahan tehdään näin:

void** c = (void**) malloc(2 * sizeof(void*));

/* ... */
size_t* solution = solve(c, f);

c on siis void** tyyppinen, mutta solve on määritelty näin: size_t* solve(void* begin, void* end) (tehtävässä annettiin solve.h, jossa se oli noin kirjoitettu.)

miksei tuossa ole näin: size_t* solve(void** begin, void** end) ?

begin[0] on siis muistiosoite johonkin toisen taulukon (taulukko a) alkuun.
begin[0]+i on sama asia, kuin a[i] ? Vai pitääkö olla *(begin[0]+i) ? Vai kenties *(*begin[0] + i) ?

Tämä syntaksi ei ole minulle ihan selvillä. Suurimmassa osassa noista yrityksistäni kääntäjä sanoo jotain tyyliin "pointer of type 'void *' used in arithmetic".

os [28.07.2007 19:02:31]

#

Voi sen noinkin toteuttaa, ajatuksena varmaankin on, että solve-funktio palauttaa osoittimen size_t*-tyyppiseen taulukkoon, jossa oikea reitti on määritelty tai NULL, jos reittiä ei ole.

Sillä, onko parametrin tyyppi void* vai void**, ei ole merkitystä. void* on osoitin, joka voi osoittaa minkä tyyppiseen dataan vain. Se täytyy muuttaa halutun tyyppiseksi (esim (size_t*), (char*), (void**)) ennen kuin sen osoittamaan tietoon pääsee käsiksi.

Muistiosoite taulukon alkuun on begin. Pointteri, johon se osoittaa on ((void**)begin)[0] ja muut alkiot vastaavasti ((void**)begin)[ i ] eli *((void**)begin+i). Et saa käyttää merkintää begin[ i ], jos begin-muuttujan tyyppi on void*.

msdos464 [28.07.2007 20:23:28]

#

Kiitos selvennyksestä. Yrityksen ja erehdyksen kautta on hieman vaikea löytää oikeaa syntaksia :D

lainaus:

Sillä, onko parametrin tyyppi void* vai void**, ei ole merkitystä.

Silti se ei saa olla void*, vaan se pitää muuttaa void**:ksi ;) Eiköhän tästä pääse taas vähän eteenpäin.

edit: voinko olettaa, että muuttuja ei saa koskaan muistiosoitteekseen nollaa? Vai pitääkö tallentaa noita arvoja int[] taulukkoon ja laittaa viimeiseksi aina -1 (että tietää mihin se loppu) ?

msdos464 [29.07.2007 00:38:53]

#

Sain tuonkin ohjelman tehtyä, mutta muistia vuotaa reilusti johonkin. Ratkaisu meni jokseenkin näin:

size_t* ownSolve(void* begin, void* end, size_t* visited, size_t* path, size_t step, size_t limit) { /* ... */
     for ( ... )
         /* ... */
          if (!in_array(visited, (int)((void**)begin)[i]) &&
                (tmp = ownSolve(((void**)begin)[i], end, addNumber(visited, (int)((void**)begin)[i]), addNumber(path, i+1), step+1, limit)))
              return tmp;
         /* ... */
}

size_t* solve(void* begin, void* end) {
     size_t *arrA = (size_t*) malloc(sizeof(size_t)),
            *arrB = (size_t*) malloc(sizeof(size_t));
     arrA[0] = arrB[0] = 0;

     size_t *result = NULL;
     size_t i;

     for (i = 1; !result && i < 50; i++)
        result = ownSolve(begin, end, arrA, arrB, 1, i);

     if (!result)
        return NULL; /* ... */
}

size_t* addNumber(size_t *arr, int num) {
     size_t* newArr = (size_t*) malloc(sizeof(size_t)*(arrayLen(arr)+1));
     size_t i;
     for (i = 0; arr[i]; i++)
         newArr[i] = arr[i];
     newArr[i] = num;
     newArr[i+1] = 0;
     return newArr;
     }

nuo taulukot "visited" ja "path" loppuvat aina nollaan. Tällä hetkellä siis luon (ehkä turhaan?) aina uuden taulukon, kun funktiota ownSolve iterroidaan (ettei tule outoja sivuvaikutuksia, kun syvemmältä tasolta palataan taaksepäin). En oikein tiedä, että miten tuon muistin vapauttaminen pitäisi tässä hoitaa.

Vai pitäisikö addNumber funktiossa käyttää sittenkin realloc funkkaria? Silloin (ehkä) saattaisi käydä niin, että taulukossa on edelliseltä kerralta merkintä, että tässä muistipaikassa olisi jo käyty, vaikka todellisuudessa siitä on otettu pakkia ja nyt mennäänkin eri polkua. Äh, vähän vaikea selittää. Pitää kokeilla että miten tuo ohjelma silloin käyttäytyy.

En kuitenkaan tiedä, että missä kohti tuota muistia vapauttaisi. Ehkäpä näin:

size_t* addNumber(size_t *arr, int num) {
     size_t* newArr = (size_t*) malloc(sizeof(size_t)*(arrayLen(arr)+1));
     size_t i;
     for (i = 0; arr[i]; i++)
         newArr[i] = arr[i];
     newArr[i] = num;
     newArr[i+1] = 0;
     free(arr); // <--
     return newArr;
     }

Tuolla tavalla muistia näköjään meni vähemmän hukkaan, mutta silti sitä häviää jonnekkin. Lisäksi debuggaaminen on hankalaa, kun on enää 7 palautuskertaa jäljellä, enkä viitsi koko lähdekoodia pasteta tänne (ettei vaan tulisi sanomista). Taitaisin vielä muutaman kohdan missä voisi vapauttaa, mutta en viitsi käyttää uutta palautuskertaa heti perään. Odottelen ensiksi vastauksia :)

Metabolix [29.07.2007 01:44:47]

#

Kylläpä olet vaikeaksi vetänyt. Vaikea noista koodeista sanoa, missä muistia vuotaa. Perustaitoihin kuuluisi itse osata jäljittää sellaiset – vaikkapa sillä aiemmin kuvaamallani tempulla, että korvataan muistinhallintafunktiot omilla vastaavilla, jotka printtaavat tietoa varattavasta ja vapautettavasta muistista.

Tuonkin ohjelman voi tehdä siististi rekursiolla eli itseään kutsuvalla funktiolla niin, että tilaa tarvitsee varata vasta silloin, kun ratkaisu on löytynyt. Tällöin voidaan aina palauttaa varattu taulu ja kirjoittaa siihen, missä kohti oltiin menossa, kun vastauksen tuottava polku löytyi. Yksi malloc => yksi free, muistivuodot ratkaistu.

Tein funktion, kirjoitin joka riville kommentin ja poistin sitten ne itse rivit. Pahoittelen spoilausta.

#include <stdlib.h>
#include <stdio.h>

size_t *ratko(void **begin, void **end, int taso)
{
	void **ptr, **uusi_begin;
	size_t *ratkaisu;

	// Jos ollaan jo oikeassa paikassa, vastaus alkakoon: {
		// Varataan ratkaisulle muistia taso+1 palaa
		// Laitetaan lopetusmerkki kohtaan [taso]
		// Palautetaan tämä
	// }

	// Käydään taulukko läpi ja etsitään rekursiivisesti: {
		// Otetaan taulukosta tämä kohta talteen
		// Merkitään tämän taulun alku käydyksi, ettei tule silmukkaa (ks. kaaviosi vasen reuna)
		// Ratkotaan rekursiivisesti eli aloitetaan haku tuosta, joka juuri otettiin talteen, tasolla (taso+1)
		// Poistetaan käyty-merkintä eli palautetaan taulun alussa ollut osoitin paikalleen
		// Jos tuli ratkaisu: {
			// Merkitään kohtaan [taso], monennestako jäsenestä päästiin eteenpäin
			// Palautetaan
		// }
	// }
	// Jos tänne joudutaan, ei ole ratkaisua (tällä reitillä)
	return NULL;
}

Täsmälleen noilla ohjeilla pitäisi syntyä toimiva funktio.

Tässä vielä varmuuden vuoksi kaaviosi mukainen testisyöte ja -ohjelma:

extern void *t0[], *t1[], *t2[], *t3[], *t4[], *t5[];
void *t0[] = {0};
void *t1[] = {t3, t0, t5, 0};
void *t2[] = {t1, 0};
void *t3[] = {t4, t2, t5, 0};
void *t4[] = {t3, 0};
void *t5[] = {0};

void **begin = t2, **end = t5;

int main(void)
{
	int i;
	size_t *p;
	p = ratko(begin, end, 0); // Haku alkaa 0. tasolta
	if (p) {
		for (i = 0; p[i] != (size_t) -1; ++i) {
			printf("%d, ", p[i]);
		}
		printf("\n");
		free(p);
	} else {
		printf("(NULL)\n");
	}
	return 0;
}

msdos464 [29.07.2007 14:43:10]

#

lainaus:

Kylläpä olet vaikeaksi vetänyt.

Tuo minun versioni on sinänsä yksinkertaisempi.. funktiolle annetaan aina syötteenä lähtö osoite, maali osoite, tähän mennessä käydyt osoitteet, polku mitä pitkin tänne on tultu, askel, ja askelien maksimi.

Automaattinen roskien keruu olisi kyllä mukava asia :) Kun ei aikaisemmin ole tarvinnut kiinnittää siihen mitään huomiota, on nyt vähän vaikea huomata, että missä kaikissa kodissa varataan aina uutta muistia.

Pitää kai yrittää tehdä tuo vähemmällä määrällä uusia taulukoita.

Metabolix [29.07.2007 15:06:23]

#

msdos464 kirjoitti:

Tuo minun versioni on sinänsä yksinkertaisempi.

Yksinkertaisempi, kun tehdään mutkikkaammin, useammalla parametrilla ja enemmillä riveillä ja vuodetaan vähän muistia siinä sivussa? Ja heti perään sanot, että "pitää kai yrittää tehdä tuo vähemmällä määrällä uusia taulukoita", siis parhaiten tuolla minun tavallani.

Yksi potentiaalinen ratkaisu on olettaa, että reitillä on enintään N askelta, ja käyttää staattista N-alkioista taulukkoa. Esimerkiksi 10000 voisi hyvinkin riittää eikä ole vielä edes paljon. Tietenkin sitten jää dynaaminen muistinhallinta tutkimatta, mutta aika usein algoritmiohjelmoinnin tehtävissä oikeasti tehdäänkin juuri näin.

msdos464 [29.07.2007 15:19:14]

#

// Merkitään tämän taulun alku käydyksi, ettei tule silmukkaa (ks. kaaviosi vasen reuna)

Mihin tuo merkittäisiin, kun funktiolla ei ole parametriä sitä varten.. tuo tieto pitää kuitenkin välittää eteenpäin, kun rekursio etenee.

Metabolix [29.07.2007 15:31:01]

#

Laitetaan taulun ensimmäiseksi jäseneksi NULL. ^^ Sekin täytyy sitten ottaa talteen silloin, että sen voi palauttaa, eikö niin? Tuollainen systeemi on aivan peruskauraa rekursiivisissa funktioissa.

msdos464 [31.07.2007 20:42:29]

#

Fiksasin tuota ohjelmaani. Enään ei varsinaisesti tule muistivuotoja, mutta tuli kylläkin muistivirheitä.. Mitä ne ovat? Tuleeko siitä muistivirhe, jos viittaan alustamattomaan taulukon indeksiin tai alustamattomaan muuttujaan?

Ohjelmasta tuli nyt noin 60 riviä, 1800 merkkiä. Merkkien lukumäärään vaikuttaa tietty muuttujien nimet (en yrittänyt mitään ennätystä).

Päädyin kuitenkin tekemään uuden funkkarin (jota kutsutaan solve funktiosta), koska tarvitsin käyttöön 2 lisätaulukkoa ja 2 lukua. Lisäksi pitää muotoilla tuosta ownSolvesta tuleva taulukko speksien mukaiseksi.

size_t* ownSolve(void* begin, void* end, size_t* visited, size_t* path, int remaining, size_t step)

Metabolix [31.07.2007 21:27:17]

#

Muistivirhe voisi näyttää vaikkapa tältä:

int *osoitin = NULL;
*osoitin = 1; // Virhe, osoitettiin osoitteeseen NULL eli 0
int taulu[5];
taulu[10] = 1; // Virhe, osoitettiin varatun alueen yli

msdos464 [31.07.2007 21:39:40]

#

Täytyy tarkastaa, ettei tule mentyöä varatun alueen yli. Seuraavassa tehtävässä tuli taas ongelmia. Olen koodannut sen speksien mukaiseksi ja valmiiksi, itsellä kääntyy jaa toimii ilman (näkyviä) ongelmia. Koulun kone kuitenkin suoltaa 60 riviä virheitä, vaikka ohjelma on (.c + .h) alle 50 riviä pitkä.

lainaus:

In file included from person.c:1:¶
person.h:11: error: syntax error before '*' token¶
person.h:11: warning: type defaults to `int' in declaration of `personConstruct'¶
person.h:11: error: ISO C forbids data definition with no type or storage class¶

(Mod. Edit. Kiitos, nämä kolme riviä riittävät. ;))

person.h

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct Person {
    int id;
    char const* name;
    char* notes;
    };

Person* personConstruct(int const id, char const *name);
int personGetId(Person const *p);
char* personGetName(Person const *p);
/* ... */

person.c

#include "person.h"
/* ... ei muita includeja */

int personGetId(Person const *p) {
    return p->id;
}
void personPrint(Person const *p, FILE *f) {
     fprintf(f, "%s (id %d), notes: %s\n", p->name, p->id, p->notes);
     }
void personSetNotes(Person *p, char const *notes) {
     free(p->notes);
     p->notes = (char*) malloc( /* ... */);
     strcpy(p->notes, notes);
     }
/* ... */

Minulle on vähän epäselvää, että miten nuo funktiot pitäisi määritellä.. vaihtoehtoja on muutamia (joista vain yksi on ANSI-C:tä?)

void personSetNotes(Person *p, char const *notes);
void personSetNotes(Person *p, const char *notes);
void personSetNotes(Person *p, const char* notes);
void personSetNotes(Person *p, char const *notes);

Ei noissa ole suuria erjoa, mutta siitä tuo kääntäjä tuntuu herjaavan.

Blaze [31.07.2007 21:41:46]

#

msdos464 kirjoitti:

Tuleeko siitä muistivirhe, jos viittaan alustamattomaan taulukon indeksiin tai alustamattomaan muuttujaan?

Jos tarkotat muistivirheellä segmentation faulttia, niin ei, mut alustamatonta muistia kannattaa silti varoa; esimerkki elävästä elämästä:

Mulla oli structi, jossa oli pointteri SDL_Surfaceen. Funktio loi uuen surfacen, piirteli siihen juttuja, ja asetti tuon structin surfacepointterin osottamaan tähän uuteen surfaceen.
Noh, jos structin surfacepointteri osotti jo johonki (!= NULL) vapautettiin luonnollisesti tuo surface, ettei vuodeta muistia.
Noh, nyt kun structia ei oltu mitenkään alustettu, saatto tuo surfacepointteri osottaa ihan mihin tahansa, jolloin annettiin SDL_FreeSurfacelle sellanen osote, johon ei oltu koskaan luotu mitään surfacea.

Aiheuttaa satunnaisesti ohjelman jumittumisia ja kaatumisia mitä ihmeellisimmillä tavoilla. Meni yks ilta selvittää, missä vika on.

Vaarallista tottua korkeamman tason kieliin, joissa muuttujat alustetaan automaagisesti.

Edit: en nyt äkkiseltään osaa sanoa, onko "char const" kelvollinen, mut ite oon aina laittanu "const char". Sillä ei oo väliä, kummalla puolen asteriski on. Ts. char *blah == char* blah == char * blah

Metabolix [01.08.2007 03:22:49]

#

Koska käytät C:tä etkä C++:aa, niin tyyppimäärittelysi struct Person luo nimenomaan tyypin struct Person eikä, kuten virheellisesti oletat, Person. Tarkista siis, että et käytä yksin sanaa Person vaan koko ilmausta. Tarkista myös jatkon varalta, että olet kotosalla tehnyt C-ohjelman etkä C++-ohjelmaa, ja jos käännät komentorivillä tai löydät IDEstäsi projektin asetuksista jonkin Compiler Parameters -laatikon, niin lisääpä parametrit -std=c99 -Wall, jotta saat vähän runsaammin virheilmoituksia kotosallakin.

struct pala {...};
struct pala m1; // Oikein
pala m2; // Väärin; ei ole olemassa tyyppiä "pala"

typedef struct pala pala; // Määritellään, että tyypin "struct pala" voi ilmaista lyhyemmin "pala"
struct pala m3; // Oikein
pala m4; // Oikein

typedef pala kakunpala; // Määritellään, että myös "kakunpala" on sama asia kuin "pala" eli sama kuin "struct pala"
kakunpala m5; // Oikein

msdos464 [01.08.2007 15:53:10]

#

lainaus:

Koska käytät C:tä etkä C++:aa, niin tyyppimäärittelysi struct Person luo nimenomaan tyypin struct Person eikä, kuten virheellisesti oletat, Person.

Ahaa :o

lainaus:

Tarkista myös jatkon varalta, että olet kotosalla tehnyt C-ohjelman etkä C++-ohjelmaa, ja jos käännät komentorivillä tai löydät IDEstäsi projektin asetuksista jonkin Compiler Parameters -laatikon, niin lisääpä parametrit -std=c99 -Wall, jotta saat vähän runsaammin virheilmoituksia kotosallakin.

Olen ainakin nimennyt tiedostot .c, eikö sen pitäisi riittää? Lisäsin parametrit -std=c99 ja -Wall.

lainaus:

Compiler: Default compiler
Building Makefile: "E:\Ohjelmat\dev-cpp\projects\kierros4\Makefile.win"
Executing make...
make.exe -f "E:\Ohjelmat\dev-cpp\projects\kierros4\Makefile.win" all
g++.exe -c person.c -o person.o -I"e:/ohjelmat/Dev-Cpp/lib/gcc/mingw32/3.4.2/include" -I"e:/ohjelmat/Dev-Cpp/include/c++/3.4.2/backward" -I"e:/ohjelmat/Dev-Cpp/include/c++/3.4.2/mingw32" -I"e:/ohjelmat/Dev-Cpp/include/c++/3.4.2" -I"e:/ohjelmat/Dev-Cpp/include" -std=c99 -Wall

cc1plus.exe: warning: command line option "-std=c99" is valid for C/ObjC but not for C++

g++.exe personmain.o person.o -o "person.exe" -L"e:/ohjelmat/Dev-Cpp/lib"

Execution terminated
Compilation successful

Käänsikö se sen nyt C++:ana? Hmm.. ainakin oli check boxissa ruksi kohdassa "Default to C++ on New Project".

Noniin, löysin asetuksen ettei käännä niitä C++:ana :D Nyt näkyy virheet omallakin koneella. Enää pitää oppia hallitsemaan nuo muistivuodot.

msdos464 [01.08.2007 18:16:03]

#

Huh, taas tulee jotain outoja ongelmia. Tehtävänannossa annettiin Othellon (aka Reversi) pelin runko, johon pitää koodata ainoastaan oma tekoäly funkkari (struct Coord aiPlay(struct Game const* game)).

koodia:

struct Game {
	int turn;
	struct Board* board;
	struct Player const* players[NUM_PLAYERS];
};
struct Board {
	struct Coord last;
	size_t width;
	size_t height;
	int* array;
};
struct Coord { int x, y; };

Valmiissa lähdekoodissa näkyy käytettävän joka puolella tällaista syntaksia:

struct Game* gameCopy(struct Game const* game) {
	struct Game* g = malloc(sizeof(struct Game));
	if (!g) return NULL;
	if (!(g->board = boardCopy(game->board))) { free(g); return NULL; }
	for (size_t i = 0; i < NUM_PLAYERS; ++i) g->players[i] = game->players[i];
	g->turn = game->turn;
	return g;
}

käytetään siis nuolta -> viittamaan tuon "olion" (ei taida olla ihan olio...) eri kenttiin (javakurssilla kutsuttiin olion kentiksi).

Nyt yritän omassa funkkarisasni (ja tiedostossa) tehdä hieman samoin:

struct Game* game2 = gameCopy(game);
game2->turn = 2;

Tulee "dereferencing pointer to incomplete type"

Tuota aiemmin yritin tulostaa boardin tilan:

 int x, y;
struct Coord c;
for (x = 0; x < 8; x++) {
    printf("\n");
    for (y = 0; y < 8; y++) {
        c = coord(x, y);
        printf("%i ", boardRead(game->board, c));
    }
}
printf("==============\n");

Tuossa tuli sama juttu, printf rivillä. Ongelma on nimenomaan game->board kohdassa. ai.c alkaa näin:

#include "ai.h"
#include <stdlib.h>
#include <stdio.h>

struct Coord;
struct Board;
struct Game;

Mitäs nyt sitten? Yritin tuota eri muodoissaan, mutta virhe oli yleensä sama.
game.board ei toiminut, ilmeisesti siitä syystä että gamea käytetään *Game pointterina.

Metabolix [02.08.2007 00:05:07]

#

Saamasi virhe "dereferencing pointer to incomplete type" tarkoittaa, että kääntäjä tietää tyypin struct Game olemassaolosta mutta ei sen sisällöstä. Siis kääntäjä tietää, että tämä muuttuja osoittaa tuollaiseen tyyppiin, mutta jotta se tietäisi myös, mikä tuo tyyppi oikeastaan on, koodissa pitäisi olla (ennen tuota riviä) varsinainen määrittely eli tuo:

struct Game {
    int turn;
    struct Board* board;
    struct Player const* players[NUM_PLAYERS];
};

Jos tämä on valmiissa otsikkotiedostossa, käytä includea.

Merkintä osoitin->jasen on sama kuin (*osoitin).jasen tai osoitin[0].jasen, eli rakenne.muuttuja on sama kuin (&rakenne)->muuttuja.

Pekka Karjalainen [02.08.2007 09:49:11]

#

msdos464 kirjoitti:

Enää pitää oppia hallitsemaan nuo muistivuodot.

Siinä onkin tekemistä :-)

Yleensä tähän käytetään automaattisia työkaluja avuksi. Jos vaikka Googlella hakee sanoja "memory leak detection", tulee tarjolle kasapäin ilmaista ja kaupallista ohjelmistoa.

Mutta kyllä se on hyvä itsekin opetella katsomaan huolella, ettei vuoda muistia tai kirjoittele kelvottomien pointterien kautta muistiin. Työkalut eivät kuitenkaan pysty niitä virheitä korjaamaan, vaikka voivat ne havaita itseä paremmin.

msdos464 [02.08.2007 15:36:07]

#

Kopeekka kirjoitti:

msdos464 kirjoitti:

Enää pitää oppia hallitsemaan nuo muistivuodot.

Siinä onkin tekemistä :-)

Yleensä tähän käytetään automaattisia työkaluja avuksi. Jos vaikka Googlella hakee sanoja "memory leak detection", tulee tarjolle kasapäin ilmaista ja kaupallista ohjelmistoa.

Yritin tuollaista etsiä, mutta ilmeisesti kehnoilla hakusanoilla :D (englanniksi kuitenkin). Valgrind tuli vastaan (sitä myös koulun servu käyttää), mutta kun on Windows alusta..

lainaus:

memory leak detection c -"c++"

Tuolla tuntuu löytyvän.

edit: ainiin, tuosta aikaisemmasta jutusta.

ai.h includeaa ai.h:n, joka includeaa game.h:n, jossa on vain "struct Game;"

game.c:ssä on sitten tuo tarkempi määrittely. Kannattaako minun vain kopioida tuo määrittely game.c:stä ai.c:hen? (ai.c on ainoa joka palautetaan, ai.h:ta ei siis kannata muutella)

Metabolix [02.08.2007 15:59:13]

#

msdos464 kirjoitti:

game.c:ssä on sitten tuo tarkempi määrittely.

Tuo .c-tiedosto on sinänsä aivan väärä paikka määrittelylle, mutta jos kerran palautat vain ai.c:n, niin ainoa vaihtoehtosi taitaa olla tosiaan kopioida kaikki tarvittavat määrittelyt sinnekin. Yleensä ohjeena olisi kyllä laittaa tuollaiset määrittelyt .h:hon, jos on tarkoitus, että muutkin kooditiedostot pääsevät nauttimaan niistä.

msdos464 [03.08.2007 15:14:24]

#

Olen saanut valmiiksi jo viikko sitten yhden ohjelman, joka omallani taas toimii moitteitta (myös C:nä käännettynä). Kouluun lähetettäessä tulee kuitenkin vain, että käyttis tappoi ohjelman, tai se kaatui omia aikojaan. Mitään debuggausta ei tule, ja on vain 2 palautuskertaa jäljellä.

Ohjelma on todella simppeli, itse kirjotettua koodia on vain 43 riviä. Sitten on yksi headeri ja toinen 23 rivinen ohjelma. Koska (ainakaan koko-) koodia ei saa julkaista netissä, voisiko joku ilmoittautua vapaaehtoiseksi, joka voisi kokeilla kääntää ja ajaa tuon ohjelman ja kertoa mahdollisesti, että miksei se toimi. Mieluiten linuxilla, koska koulussa on linux. Voisin lähettää sourcet suoraan yv:nä tai sitten laittaa zippinä nettiin.

Miten olisi? Deadline olisi maanantaina, eli viikonlopun aikana olisi hyvä saada tähänkin ongelmaan selvyys.

jlaire [03.08.2007 15:37:51]

#

No säännöthän ovat aivan selvät:

lainaus:

Publishing your own exercise code or part of it during the course is forbidden and is considered as cheating. Don't make your code available on www pages or post it to newsgroups.

msdos464 kirjoitti:

Miten olisi? Deadline olisi maanantaina, eli viikonlopun aikana olisi hyvä saada tähänkin ongelmaan selvyys.

No siitä vaan tutkimaan. Vaikka täältä löytyykin ihmeen avuliasta porukkaa, kannattaisi yrittää itse vähän enemmän jos haluat jotain oppiakin.

msdos464 [03.08.2007 16:46:23]

#

funktio kirjoitti:

No säännöthän ovat aivan selvät:

lainaus:

Publishing your own exercise code or part of it during the course is forbidden and is considered as cheating. Don't make your code available on www pages or post it to newsgroups.

msdos464 kirjoitti:

Miten olisi? Deadline olisi maanantaina, eli viikonlopun aikana olisi hyvä saada tähänkin ongelmaan selvyys.

No siitä vaan tutkimaan. Vaikka täältä löytyykin ihmeen avuliasta porukkaa, kannattaisi yrittää itse vähän enemmän jos haluat jotain oppiakin.

"jos haluat jotain oppiakin" ? :S Tuolle kommentille voisin vuodattaa pitkänkin viestin, mutta eiköhän lyhyemmälläkin tule asia selväksi. Kyllä minä olen oppinut aina virheistäni, eikä täällä ole kahta kertaa tarvinnut kysyä samasta asiasta.

Jos en täällä kyselisi, toinen vaihtoehto olisi mennä koululle kyselemään assareilta aina duunin jälkeen (viikossa on vain yksi päivä kun ehtisin töiden jälkeen, muuten pitäisi jättää työpäiviä väliin). Siellä on varmaan assareiltakin muitakin heppuja kyselemässä, niin vastausta / apua saisi odottaa 5 - 15 minuuttia. Ainakin javakurssilla asia oli näin (pari kertaa kaverin mukana kävin, itse en tarvinnut avustusta ;) ).

Olen tosiaan kiitollinen, että on ollut näin avuliasta porukkaa. Helpottaa elämää kummasti (enkä tarkoita tällä sitä, että täältä annettaisiin tehtäviin valmiita ratkaisuja). Yleensähän kysymykset ovat olleet enemmänkin jotain perusasioita (kuten [01.08.2007 18:16:03] ajankohtana postaamani kysymys).

Ja mitä tuohon koodin julkaisu sääntöön tulee, yleensä olen julkaissut mahdollisimman vähän omasta ohjelmastani. No tämäkin on tietysti kiellettyä, mutta säännön kirjaimellinen noudattaminen olisi mielestäni vähän hölmöä. Eri asia olisi, jos tekisi nettisivut jonne laittaisi noiden tehtävien ratkaisut kaikkien nähtäville. Tämä ei suinkaan ole minun tarkoitukseni, eikä niitä suoraan kopioimalla kukaan edes oppisi mitään. Jos joku muukin kurssilainen on tästä topickista apua etsinyt, uskoisin hänenkin oppineen yhtä ja toista. Annetut ohjeet ovat kuitenkin hyvin "yleisessä" muodossa, eivätkä mitenkään suoria ratkaisuja johonkin ongelmaan.

Esim.

lainaus:

fscanf:lle pitää syöttää osoite eli
fscanf(infile, "%i", &tmpId).

lainaus:

scanf palauttaa "osumien" määrän, eli esim.

scanf("%d", &luku);

palauttaa syötteenä kokonaisluvun saadessaan numeron 1. Mikäli syötteestä ei saa järkevästi numeroa, se palauttaa 0. Mikäli luettava virta/tiedosto loppuu kesken tai tapahtuu virhe, scanf palauttaa EOFin.

lainaus:

Sillä, onko parametrin tyyppi void* vai void**, ei ole merkitystä. void* on osoitin, joka voi osoittaa minkä tyyppiseen dataan vain. Se täytyy muuttaa halutun tyyppiseksi (esim (size_t*), (char*), (void**)) ennen kuin sen osoittamaan tietoon pääsee käsiksi.

lainaus:

Merkintä osoitin->jasen on sama kuin (*osoitin).jasen tai osoitin[0].jasen, eli rakenne.muuttuja on sama kuin (&rakenne)->muuttuja.

Pointti tuli varmaankin selväksi :)

funktio kirjoitti:

No siitä vaan tutkimaan.

Mitenkäs tutkin, kun kääntäjä ja linkkeri sanoo että kaikki ok. Ohjelma toimii muutenkin speksien mukaan. Jos rupean printf:llä tulostelemaan debuggaus tietoa, tarvisee minun tehdä useampi lähetys tuonne Gobliniin, jonne kuitenkin on sangen rajoitetusti noita lähetyskertoja. Ainoa info mitä Goblin tarjoaa, on tämä:

lainaus:

Your program crashed, or was killed by the operating system!

On keinot vähän vähissä..

JoinTuanJanohon [03.08.2007 22:37:40]

#

Jaa, melko monen vuosikymmenen kokemuksella olen sitä mieltä, että valmiita rautalankamallit opettavat enemmän, kuin ympäri pyöreät jaarittelut, että tee niin ja noin, ja googleta se sieltä, ja sen toisen löydät kysymälle Jeevekseltä, jne.

Itselläni ohjelmoinnin lisäksi on muitakin ammatteja. Ohjelmointi ei mitenkään poikkea vaativuudessaan esimerkiksi hitsaustöistä öljynporauslautalla. Kummassakin pitää osata. Oppiminen on nopeampaa kun vanha partajeesus näyttää mallia, miten vitosen saumaa vedetään lakiasennossa. Voisihan sitä sitten joku propellihattu sinnikkäästi opetella itse hilaamaan katseen kestävää saumaa, mutta oppimisprosessiin saattaisi mennä koko aktiivisen työuran aika. Suhteellinen ilo oppia työnsä hetkeä ennen eläkkeelle siirtymistä.

Mjaah, darkoitan siis sitä, että me ohjelmoinnista jotain ymmärtävät edustamme yhdessä, kollektiivisesti, suurta paskahousua, jonka sisällä informaation ja eri tekniikoiden vaihdon soisi olla suurempi.

Ja sitä paitsi, suurin osa ATK-kysymyksistä liittyy toisarvoisiin seikkoihin. Kuten esim. miten jonkun Windows-paskan saa kokoruututilaan, ocv. Koodauksessa pitemmälle ehtineet saattavat haluta kysymyksiinsä teoreettisempia vastauksia. Kuten esim. miten se fft-paska sitten käytännössä optimoi kahden 64 kilotavun kokoisten lukujen jakolaskun? DSP:stä opittu ehdollisen vähennyksen algoritmi kyllä tuottaa jakolaskun tuloksen ja modulon, mutta muutaman rivin algoritmi vaivaa tulosta kauemmin, kuin mitä oli edellinen nälkävuosi, jne.

Siis, suoria vastauksia suoriin kysymyksiin, koska kaikki vastaukset ovat opittuja. Mielenkiintoisiin kysymyksiin mukaan myös mielellään teoriaa ja taustaa.


Sivun alkuun

Vastaus

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

Tietoa sivustosta