Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Merkkijonon palauttaminen funktiosta pääohjelmaan C-kielessä

Sivun loppuun

Ville [16.06.2009 23:07:14]

#

Olen koettanut opetella palauttamaan numero- ja merkkijonotaulukoita funktioista.
Numerotaulukot nyt jotenkin menevät mutta näiden merkkijonojen kanssa ei meinaa hommasta nyt tulla mitään :(

Laitan tähän mukaan pikku koodi kyhäelmän joka kyllä kääntyy ja saan exen ajettua mutta saan seuraavanlaisen virheilmoituksen :

[Warning] function returns address of local variable

Ymmärrän kyllä mitä tuo virheilmoitus sanoo.
"Funktio palauttaa paikallisen muuttujan osoitteen", mutta eikös taulukon kanssa osoite kuulukin palautua, vai olenko taas sekoittanut asioita keskenään?

Vai liittyykö tuo siihen että taulukko on esitelty ja otettu kayttöön funktion sisällä ja pääohjelmassa siitä ei tiedetä mitään.
Koetin taulukosta globaaliakin tehdä mutta eipä auttanut ...

Funktion palauttama arvo ei kyllä ole se teksti jonka funtiossa taulukkoon sijoitetaan.
Jotenkin on sellainen tunne että osoittimiin tässä taas törmätään :(
Varmasti ikävin asia C kielen perusteissa !

Tässä siis koodi:

#include<stdio.h>

char* teeTervehdys(void);

int main()
{
    printf("Tervehdys fuktiosta, %s vaan\n", teeTervehdys());
    system("pause");
    return 0;
}
char* teeTervehdys(void)
{
      char viesti[] = " Heippa";
      return viesti;
}

Sisuaski [16.06.2009 23:44:06]

#

Ongelma on siinä, että funktioden paikalliset muuttujat, taulukot mukaan lukien, ovat olemassa vain niin kauan, kuin funktiota suoritetaan.

Kun funktiosta teeTervehdys siis palataan, tuhoutuu taulukko viesti, ja siihen osoittavat osoittimet, tässä kohtaa siis funktion paluuarvo, muuttuvat käyttökelvottomiksi (ts. jos niiden kautta yritetään lukea jotain, voi tuloksena olla mitä tahansa moskaa).

Helpoin ratkaisu lienee lisätä muuttujan viesti määrittelyn eteen sana static:

static char viesti[] = " Heippa";

Tällöin taulukkoa ei tuhota funktiokutsun loppuessa, vaan sitä säilytetään jatkuvasti muistissa.

Toinen vaihtoehto olisi varata funktiossa tilaa merkkijonolle malloc-funktiolla, kopioida haluttu merkkijono varattuun tilaan, ja palauttaa osoitin luotuun uuteen merkkijonoon. (POSIX-ympäristössä tämä operaatioiden sarja onnistuu helpointen funktiolla strdup.) Tämä on kuitenkin virhealtista, koska joudut vapauttamaan luomasi merkkijonon aina kutsuttuasi funktiota.

Teuro [17.06.2009 00:07:53]

#

Kolmas vaihtoehto sopii vielä mukaan.

#include <stdio.h>

char *teeTervehdys(void);

int main(){
    printf("Tervehdys fuktiosta, %s vaan\n", teeTervehdys());
    system("pause");
    return 0;
}

char *teeTervehdys(void){
      char *viesti = " Heippa";
      return viesti;
}

Ville [17.06.2009 07:59:40]

#

Kiitoksia taas vastauksista

Näköjään aika lähellä tuota Teuron mallia täällä on käyty. Eli siinä siis palautetaan osoitin.

Sisuaski kirjoitti:

Ongelma on siinä, että funktioden paikalliset muuttujat, taulukot mukaan lukien, ovat olemassa vain niin kauan, kuin funktiota suoritetaan.

Kun funktiosta teeTervehdys siis palataan, tuhoutuu taulukko viesti, ja siihen osoittavat osoittimet, tässä kohtaa siis funktion paluuarvo, muuttuvat käyttökelvottomiksi (ts. jos niiden kautta yritetään lukea jotain, voi tuloksena olla mitä tahansa moskaa).

No näin arvelinkin ja kokeilin sellaistakin että tein taulukko viestistä globaalin muuttujan jotta se olisi kaikkien näkyvillä mutta sekään ei auttanut.
Miksi?

zokier [17.06.2009 08:49:53]

#

Tässä esimerkkinä viesti globaalina muuttujana, toimii

http://codepad.org/3eE7wf7X

Sinänsähän nyt teeTervehdys palauttaa aina saman osoittimen, joten olisi käytännössä sama viitata suoraan viestiin mainissa. MINUSTA järkevin vaihtoehto olisi tuo Sisuaskin mainitseman malloc tms muistinvaraus, mutta tosiaankin muisti pitää silloin muistaa vapauttaa. C-ohjelmoinnin ihanuutta se.

Teinpä vielä esimerkin siitäkin kun olin innokas...

http://codepad.org/JGouicVM


ps. en ole koskaan hirveästi c:tä koodannut niin saattaa olla väärinkin.

Metabolix [17.06.2009 09:14:01]

#

Ville kirjoitti:

No näin arvelinkin ja kokeilin sellaistakin että tein taulukko viestistä globaalin muuttujan jotta se olisi kaikkien näkyvillä mutta sekään ei auttanut. Miksi?

Teit siinä varmaankin jonkin virheen.

Seuraavassa koodissa on erilaisia tapoja (oikeita ja vääriä) tekstin palauttamiseen.

#include <stdio.h>

// Paikallinen teksti:
char *v1(void) {
	char v[] = "Viesti";
	return v;
}

// Vakioteksti ilman const-määrettä:
char *v2(void) {
	return "Viesti";
}
// Vakioteksti ilman const-määrettä ylimääräisen sijoituksen kanssa:
char *v3(void) {
	char *v = "Viesti";
	return v;
}

// Vakioteksti:
const char *v4(void) {
	return "Viesti";
}
// Vakioteksti ylimääräisen sijoituksen kanssa:
const char *v5(void) {
	const char *v = "Viesti";
	return v;
}

// Globaali staattinen teksti:
char g[] = "Viesti";
char *v6(void) {
	return g;
}
// Paikallinen staattinen teksti:
char *v7(void) {
	static char s[] = "Viesti";
	return s;
}

// Funktio, joka kutsuu esitettyjä funktioita ja tulostaa tiedot.
void f(int i, const char *(*v)(void), int r) {
	if (!r) {
		printf("v%d: %s @ %p, ", i, v(), v());
		// Tulostetaan toinen teksti uudella funktiokutsulla,
		// jotta ollaan eri kohdassa pinoa.
		return f(i, v, 1);
	}
	printf("toisella tasolla: %s @ %p\n", v(), v());
}

int main() {
	// Testataan kaikkia funktioita.
	const char *(*v[])(void) = {v1, v2, v3, v4, v5, v6, v7, 0};
	int i;
	for (i = 0; v[i]; ++i) {
		f(i+1, v[i], 0);
	}
}

ohjelma kirjoitti:

v1: 1???81??A @ 0xbf8e3111, toisella tasolla: 1???0??1??~?0??[ @ 0xbf8e30f1
v2: Viesti @ 0x80485e0, toisella tasolla: Viesti @ 0x80485e0
v3: Viesti @ 0x80485e0, toisella tasolla: Viesti @ 0x80485e0
v4: Viesti @ 0x80485e0, toisella tasolla: Viesti @ 0x80485e0
v5: Viesti @ 0x80485e0, toisella tasolla: Viesti @ 0x80485e0
v6: Viesti @ 0x8049720, toisella tasolla: Viesti @ 0x8049720
v7: Viesti @ 0x8049727, toisella tasolla: Viesti @ 0x8049727

Tulosteesta nähdään, että ensimmäinen funktio palauttaa eri muistiosoitteen eri kutsukerroilla ja siksi toimii väärin. Neljä seuraavaa taas palauttavat saman osoitteen eli viittauksen samaan "Viesti"-tekstiin. Kaksi viimeistä palauttavat kumpikin oman osoitteensa, joka kuitenkin pysyy samana kutsukertojen välillä ja siksi toimii.

Kuusi viimeistä siis toimivat tässä tilanteessa oikein. Kuitenkin näistä kaikkien paitsi staattista tai lokaalia taulukkoa käyttävien paluuarvon merkkien muuttaminen kaataa ohjelman, koska "Viesti" on vakioteksti eikä sitä saa muuttaa. Siispä funktiot v2 ja v3 ja siinä sivussa myös Teuron esimerkki ovat virheellisiä; vakiotekstin kanssa kuuluu käyttää const-avainsanaa kuten funktioissa v4 ja v5. Jos tekstin pitää olla muokattava, oikea ratkaisu on v6 tai v7, ja näistä v6 on sikäli parempi, että muokkauksen voi kohdistaa v6():n sijaan suoraan globaaliin taulukkoon g, jolloin taulukon koko selviää sizeof-operaattorilla.

Ville [17.06.2009 10:21:48]

#

Ok...

Nyt tuli vähän sellainen tunne että meikäläinen koodattiin sinne kuuluisalle kebab kioskille, varsinkin tuon Metabolix:n kirjoittaman vastauksen myötä :p

Ei, älä ymmärrä väärin. Vastauksesi on hyvä, täytyy vain koettaa se sisäistää täällä päässä.

Olisin vielä kysynyt tuosta zokier:n kirjoittamasta ensimmäisen linkin koodista sellaista että kun suoritetaan merkkijonon kopiointi taulukkoon funtiolla strncpy() ja käytetään vakiota MAX_CHARS kopioitavien merkkien määrän määrityksessä, siis näin:

strncpy(viesti, "heippa", MAX_CHARS);

Tässä tapauksessahan kopioitava teksti "heippa" on lyhyempi kuin vakion arvo.
Entä jos tekstinä olisikin "esimerkki!" = 10 merkkiä. Vakion määrä ei ylity joten kopiointi onnistuu ja tulostuu ruudulle.
Mutta nyt nollatavua ei enää saada mahtumaan tuohon taulukkoon. Olenko nyt ollenkaan jäljillä ?
Pitäisikö kirjoittaa näin ?

strncpy(viesti, "heippa", MAX_CHARS-1);

Tai hetkinen...tuleeko tuossa kopioinnissa edes sitä nollatavua "automaagisesti" sanan perään.

Kylläpä on kimurantit nämä C kielen merkkijono käsittelyt. Alkaa tosiaan kaipaamaan muuttuja tyyppiä String :p

zokier [17.06.2009 11:04:58]

#

tuossa taisi minulla tosiaan tulla virhe. tuo

strncpy(viesti, "heippa", MAX_CHARS-1);

ei ymmärtääkseni auta koska '\0'aa ei siltikään mistään sinne loppuun ilmesty. olisikohan oikea tapa ratkaista ongelma lisäämällä rivi

viesti[MAX_CHARS-1] = '\0';

johonkin sopivaan väliin. strncpy lisää nollatavut kyllä silloin kun annettu stringi on lyhyempi kuin annettu pituus.


tämä

strncpy(viesti, "heippa", MAX_CHARS-1);

toimisi kyllä jos alustaisi viestin nollaksi kuten hyvä tapa olisi ennen käyttöä, siihen kai oli joku valmis funktiokin.

Metabolix [17.06.2009 11:07:21]

#

http://www.cplusplus.com/reference/clibrary/cstring/strncpy/:

Copies the first num characters of source to destination. — — No null-character is implicitly appended to the end of destination.

Oikea tapa on siis kopioida MAX_CHARS - 1 merkkiä ja lisätä itse vielä nollamerkki:

strncpy(viesti, "heippa", MAX_CHARS - 1);
viesti[MAX_CHARS - 1] = 0;

Jos on varmaa, että nollamerkki on jo ennestään paikallaan, se ei tietenkään katoa mihinkään eikä sitä tarvitse lisätä.

char viesti[MAX_CHARS] = {0};             // nollamerkkiä täynnä
strncpy(viesti, "heippa", MAX_CHARS - 1); // nollamerkki pysyy
strncpy(viesti, "moikka", MAX_CHARS - 1); // nollamerkki pysyy
strncpy(viesti, "jumps", MAX_CHARS - 1);  // nollamerkki pysyy

Jos asiasta on mitään epäselvyyttä, kannattaa kuitenkin lisätä se merkki aina. Ylimääräinen muistioperaatio pari kertaa ohjelman aikana on halpa hinta varmasta toimimisesta.

Ville [17.06.2009 12:10:29]

#

Kiitoksia vastauksista, tässähän asioita alkaa kirkastumaan.

Metabolix kirjoitti:

strncpy(viesti, "heippa", MAX_CHARS - 1);
viesti[MAX_CHARS - 1] = 0;

Vielä tuli mieleen tuossa ylläolevasta koodista se että jos MAX_CHARS on määritelty vakioksi ohjelman alussa niin miten siitä voi vähentää tuon ykkösen.
Eipä tullut itsellenikään mieleen kun sen keskusteluun kirjoitin kuin vasta nyt.

Homma kyllä toimii mutta mikäs virka tuollaisella vakiolla on jos arvoa pääsee muuttamaan?
Niin tuo ykkönenkin taidetaan käsittää vakioksi tuossa mutta silti ...

Metabolix [17.06.2009 12:18:31]

#

Ei se MAX_CHARS miksikään vähentämisestä muutu. 10 - 1 tekee 9, eikä tämä muuta 10:n arvoa miksikään: seuraavallakin kerralla 10 - 1 on sama 9. Aivan samasta syystä MAX_CHARS - 1 on aina saman verran.

Oletko mitään C++-opasta lukenut? Kannattaisi, niistä pitäisi selvitä muuttujan ja arvon ero.

Teuro [17.06.2009 12:25:25]

#

Eihän normaalin muuttujankaan arvo muutu mihinkään vastaavassa tilanteessa. Voithan aina kokeilla.

#include <iostream>

int main(){
    int a = 3;
    const int A = 3;

    std::cout << "a - 1:n arvo = " << (a-1) << std::endl; /* 2 */
    std::cout << "A - 1:n arvo = " << (A-1) << std::endl; /* 2 */
    std::cout << "a:n arvo = " << a << std::endl; /* 3 */
    std::cout << "A:n arvo = " << A << std::endl; /* 3 */

    return EXIT_SUCCESS;
}

Ville [17.06.2009 13:25:05]

#

Metabolix kirjoitti:

Ei se MAX_CHARS miksikään vähentämisestä muutu. 10 - 1 tekee 9, eikä tämä muuta 10:n arvoa miksikään: seuraavallakin kerralla 10 - 1 on sama 9. Aivan samasta syystä MAX_CHARS - 1 on aina saman verran.

No perkutarallaa, näinhän se on !
Nyt on selkeesti paussin paikka, joten ei muuta ku sufeelle.

Metabolix kirjoitti:

Oletko mitään C++-opasta lukenut? Kannattaisi, niistä pitäisi selvitä muuttujan ja arvon ero.

Tuota, tuota. En ole c++ opasta lukenut ja näillä näkymillä ei ole aikomustakaan.
Koetetaan nyt tästä C:stä jotenkin perusteet saada haltuun...

Metabolix [17.06.2009 14:09:59]

#

Ville kirjoitti:

Tuota, tuota. En ole c++ opasta lukenut ja näillä näkymillä ei ole aikomustakaan.

Aivan, ajatusvirhe. C-opasta sitten, sisällön pitäisi olla näiltä osin sama. Oikeastaan nämä muuttuja-asiat voi lukea aivan hyvin C++-oppaastakin; muuttujat, taulukot ja tavalliset komentorakenteet eivät ole muuttuneet miksikään. Myös täällä vast'ikään julkaistujen kuuden C++-oppaan tieto on tulostusta ja muita selvästi C++:n uutuuksia lukuun ottamatta C99-yhteensopivaa ja siis pienillä muutoksilla myös ANSI C -yhteensopivaa.

Mutta mitäpä sitä enää lukemaan, jos asia tuli jo viesteistä selväksi. ;)


Sivun alkuun

Vastaus

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

Tietoa sivustosta