Mikä on etu palauttaa osoitin rakenteeseen verrattuna koko rakenteen palauttamiseen return
lauseke funktiosta?
Puhun toiminnoista, kuten fopen
ja muista matalan tason funktioista, mutta todennäköisesti on myös korkeamman tason toimintoja, jotka palauttavat viitteitä rakenteisiin.
Uskon, että tämä on pikemminkin suunnitteluvaihtoehto kuin vain ohjelmointikysymys, ja olen utelias tietämään enemmän näiden kahden menetelmän eduista ja haitoista.
Yksi syistä, joiden mielestäni olisi etu palauttaa osoitin rakenteeseen, on pystyä kertomaan helpommin, jos toiminto epäonnistui palauttamalla NULL
osoittimen.
Oletan vaikeampi tai vähemmän tehokas palauttaa täysi rakenne, joka on NULL
. Onko tämä pätevä syy?
Kommentit
Vastaa
Siellä ovat useita käytännön syitä, miksi toiminnot, kuten fopen
palaavat osoittimet struct
-tyyppien esiintymien sijaan:
- Haluat piilottaa
struct
-tyypin esityksen käyttäjältä; - varaat objektin uudelleen dynaamisesti;
- uudelleen viitataan objektin yksittäiseen esiintymään useiden viitteiden kautta;
Tyyppien FILE *
kaltaisissa tapauksissa se johtuu siitä, että et t haluat paljastaa käyttäjän tyypin edustuksen yksityiskohdat – a FILE *
obje ct toimii läpinäkymättömänä kahvana, ja välität sen vain erilaisille I / O-rutiineille (ja vaikka FILE
toteutetaan usein nimellä struct
tyyppi, sen ei tarvitse olla
Voit siis paljastaa keskeneräinen struct
-tyypin otsikossa jonnekin:
typedef struct __some_internal_stream_implementation FILE;
Vaikka et voi julistaa puutteellisen tyyppistä esiintymää, voit ilmoittaa sen osoittimen. Joten voin luoda FILE *
ja määrittää sen fopen
, freopen
jne. Kautta. , mutta en voi ”manipuloida suoraan kohdetta, johon se osoittaa.
On myös todennäköistä, että fopen
-toiminto varaa FILE
objekti dynaamisesti käyttämällä malloc
tai vastaavaa. Siinä tapauksessa on järkevää palauttaa osoitin.
Lopuksi on mahdollista, että tallennat jonkinlaisen tilan struct
-objektiin, ja sinun on asetettava tämä tila saataville useisiin eri paikkoihin. Jos palautat struct
-tyyppisiä esiintymiä, nämä esiintymät olisivat erillisiä muistissa olevia objekteja toisistaan ja lopulta pääsisivät synkronoinnista. Palauttamalla osoitin yhteen objektiin, kaikki viittaavat samaan objektiin.
Kommentit
- Erityinen etu osoittimen käytöstä läpinäkymätön tyyppi on, että rakenne itsessään voi muuttua kirjastoversioiden välillä, eikä sinun ’ tarvitse kääntää soittajia uudelleen.
- @Barmar: ABI-vakaus on todellakin C: n valtava myyntipiste, eikä se olisi yhtä vakaa ilman läpinäkymättömiä osoittimia.
Vastaa
Rakenteen voi palauttaa kahdella tavalla. Voit palauttaa kopion tiedoista tai palauttaa niihin viitteen (osoittimen).Yleensä parempana on palauttaa (ja yleensä ohittaa) osoitin parista syystä.
Ensinnäkin rakenteen kopioiminen vie paljon enemmän suorittimen aikaa kuin osoittimen kopiointi. Jos tämä on jotain koodisi tekee usein, se voi aiheuttaa huomattavia suorituskykyeroja.
Toiseksi, riippumatta siitä, kuinka monta kertaa kopioit osoitinta, se osoittaa edelleen muistissa olevaa samaa rakennetta. Kaikki siihen tehdyt muutokset heijastuvat samaan rakenteeseen. Mutta jos kopioit itse rakenteen ja teet sitten muutoksen, muutos näkyy vain kyseisessä kopiossa . Koodi, jolla on eri kopio, ei näe muutosta. Joskus, hyvin harvoin, tämä on se, mitä haluat, mutta useimmiten se ei ole, ja se voi aiheuttaa virheitä, jos erehdyt siihen.
Kommentit
- Osoittimella palauttamisen haittapuoli: Nyt ’ sinun on seurattava kyseisen objektin omistajuutta ja mahdollista vapauta se. Osoittimen kohdentaminen voi myös olla kalliimpaa kuin nopea kopiointi. Täällä on paljon muuttujia, joten osoittimien käyttö ei ole yleisesti parempi.
- Lisäksi osoittimet ovat nykyään 64 bittiä useimmissa työpöytä- ja palvelinympäristöissä. Olen ’ nähnyt urallani enemmän kuin muutaman rakenteen, jotka mahtuvat 64 bittiin. Joten voit ’ t aina sanoa, että osoittimen kopioiminen maksaa vähemmän kuin rakenneen kopiointi.
- Tämä on enimmäkseen hyvä vastaus , mutta olen eri mieltä osasta joskus, hyvin harvoin, tämä on mitä haluat, mutta useimmiten se ’ ei ole – päinvastoin. Osoittimen palauttaminen sallii useita erilaisia ei-toivottuja sivuvaikutuksia ja useita erilaisia ikäviä tapoja saada osoitin väärin omistukseen. Tapauksissa, joissa suorittimen aika ei ole niin tärkeä, pidän parempana kopiomuotoa, jos se on vaihtoehto, se on paljon vähemmän virhealtista.
- On huomattava, että tämä todella koskee vain ulkoisia sovellusliittymiä. Sisäisiä toimintoja varten jokainen marginaalisesti pätevä viimeisten vuosikymmenien kääntäjä kirjoittaa funktion, joka palauttaa suuren rakenteen ottamaan osoittimen lisäargumenttina ja rakentamaan objektin suoraan sinne. Muuttamattomien ja muuttuvien argumentit on tehty riittävän usein, mutta luulen, että voimme kaikki olla samaa mieltä siitä, että väite, jonka mukaan muuttumattomia tietorakenteita ei melkein koskaan ole, mitä haluat, ei pidä paikkaansa.
- Voit mainita myös kokoamisen paloseinät viitteiden ammattilaisena. Suurissa ohjelmissa, joissa on laajasti jaetut otsikot, epätäydelliset tyypit ja toiminnot estävät tarpeen kääntää uudelleen aina, kun toteutustiedot muuttuvat. Parempi kokoomakäyttäytyminen on itse asiassa kapseloinnin sivuvaikutus, joka saavutetaan, kun rajapinta ja toteutus erotetaan. Palautus (ja välittäminen, osoittaminen) arvon mukaan edellyttää toteutustietoja.
Vastaus
Muiden vastausten lisäksi , joskus kannattaa palauttaa pieni struct
arvon mukaan. Voit esimerkiksi palauttaa yhden dataparin ja siihen liittyvän virhekoodin (tai menestyskoodin).
Esimerkiksi fopen
palauttaa vain yksi data (avattu FILE*
) ja virheen sattuessa antaa virhekoodin errno
pseudo-globaali muuttuja. Mutta olisi ehkä parempi palauttaa struct
, jossa on kaksi jäsentä: kahva FILE*
ja virhekoodi (joka asetetaan, jos tiedoston kahva on NULL
). Historiallisista syistä näin ei ole (ja virheistä ilmoitetaan errno
globaalissa, joka on nykyään makro).
Huomaa, että Go-kielellä on hieno merkintä palauttaakseen kaksi (tai muutama) arvo.
Huomaa myös, että Linux / x86-64: ssä ABI ja soittokäytännöt (katso sivu x86-psABI ) määrittävät, että struct
(esim. osoitin ja kokonaisluku, tai kaksi osoittinta tai kaksi kokonaislukua) palautetaan kahden rekisterin kautta (ja tämä on erittäin tehokasta eikä mene muistiin).
Joten uudessa C-koodissa pienen C-arvon palauttaminen struct
voi olla luettavampaa, langankiertoystävällisempää ja tehokkaampaa.
Kommentit
- Itse asiassa pienet rakenteet pakataan osiin
rdx:rax
. Jotenstruct foo { int a,b; };
palautetaan pakattuna osioonrax
(esim. Shift / tai), ja se on purettava shift / mov-pakkauksella. Tässä ’ on esimerkki Godboltista . Mutta x86 voi käyttää 64-bittisen rekisterin matalia 32 bittiä 32-bittisiin operaatioihin huolimatta suurista biteistä, joten se ’ on aina liian huono, mutta ehdottomasti huonompi kuin käyttämällä 2 rekisteröi suurimman osan ajasta 2-jäsenisille rakenteille. - Liittyvät: bugs.llvm.org/show_bug.cgi? id = 34840
std::optional<int>
palauttaa loogisen arvonrax
yläosassa, joten tarvitset 64-bittisen maskin vakiona sen testaamiseksitest
-toiminnolla. Tai voit käyttääbt
. Mutta se on perseestä soittajalle ja soittaja vertaa käyttämäändl
, joka kääntäjien tulisi tehdä ” private ” -toiminnot. Liittyy myös: libstdc ++ ’ sstd::optional<T>
ei ole ’ t triviaalisesti kopioitavissa, vaikka T on , joten se palaa aina piilotetun osoittimen kautta: stackoverflow.com/questions/46544019/… . (libc ++ ’ s on triviaalisesti kopioitavissa) - @PeterCordes: asiaan liittyvät asiat ovat C ++, ei C
- Hups, oikea. Sama pätee tarkalleen kohtaan
struct { int a; _Bool b; };
C: ssä, jos soittaja haluaa testata loogista, koska triviaalisesti kopioitavissa C ++ -rakenteissa käytetään samaa ABI: tä kuin C. - klassinen esimerkki
div_t div()
vastaus
Olet oikealla tiellä
Molemmat mainitsemasi syyt ovat päteviä:
Yksi syy ajatus, että tämä olisi etu palauttaa osoitin rakenteeseen, on pystyä kertomaan helpommin, jos funktio epäonnistui palauttamalla NULL-osoitin.
NULL-arvon TÄYDEN rakenteen palauttaminen olisi vaikeampi. tai vähemmän tehokas. Onko tämä pätevä syy?
Jos sinulla on tekstuuri (esimerkiksi) jossain muistissa ja haluat viitata siihen useissa paikoissa ohjelmoida; ei ole viisasta tehdä kopiota joka kerta, kun haluat viitata siihen. Sen sijaan, jos yksinkertaisesti ohitat osoittimen ympärille viitataksesi tekstuuriin, ohjelmasi toimii paljon nopeammin.
Suurin syy on dynaaminen muistin allokointi. Usein, kun ohjelma käännetään, et ole varma, kuinka paljon muistia tarvitset tietyille tietorakenteille. Kun näin tapahtuu, käytettävä muistin määrä määritetään ajon aikana. Voit pyydä muistia mallocilla ja vapauta se, kun olet lopettanut ilmaisen käytön.
Hyvä esimerkki tästä on lukeminen käyttäjän määrittelemästä tiedostosta. Tällöin sinulla ei ole idea, kuinka suuri tiedosto voi olla, kun käännät ohjelmaa. Voit selvittää, kuinka paljon muistia tarvitset vain, kun ohjelma on todella käynnissä.
Sekä malloc- että ilmaiset palautusosoittimet muistin sijainteihin. jotka käyttävät dynaamista muistin allokointia, palauttavat osoittimet sinne, missä he ovat luoneet rakenteensa muistiin.
Kommenteissani näen myös olevan kysymys siitä, voitko palauttaa rakenteen funktiosta. Voit todellakin tehdä tämän. Seuraavien pitäisi toimia:
struct s1 { int integer; }; struct s1 f(struct s1 input){ struct s1 returnValue = xinput return returnValue; } int main(void){ struct s1 a = { 42 }; struct s1 b= f(a); return 0; }
Kommentit
- Kuinka on mahdollista olla tietämättä, kuinka paljon muistia tietty muuttuja tarvitaan, jos sinulla on jo määritelty rakennetyyppi?
- @JenniferAnderson C: llä on käsite epätäydellisistä tyypeistä: tyypin nimi voidaan ilmoittaa, mutta sitä ei ole vielä määritelty, joten se ’ s kokoa ei ole käytettävissä. En voi ilmoittaa kyseisen tyyppisiä muuttujia, mutta voin ilmoittaa tälle tyypille osoittimet , esim.
struct incomplete* foo(void)
. Tällä tavoin voin ilmoittaa toiminnot otsikossa, mutta määritellä vain C-tiedostossa olevat rakenteet, mikä mahdollistaa kapseloinnin. - @amon Näin funktioiden otsikot (prototyypit / allekirjoitukset) ilmoitetaan ennen niiden ilmoittamista, miten ne työ tehdään todella C: ssä? Ja on mahdollista tehdä sama asia C: n rakenteille ja liitoksille
- @JenniferAnderson ilmoitat funktioiden prototyypit (toiminnot ilman runkoja) otsikkotiedostoihin ja voit sitten kutsua näitä funktioita toisessa koodissa tuntematta funktioiden runkoa, koska kääntäjän on vain tiedettävä, kuinka järjestää argumentit ja kuinka hyväksyä paluuarvo. Kun linkität ohjelman, sinun on todella tiedettävä funktio määritelmä (eli rungon kanssa), mutta sinun on käsiteltävä se vain kerran. Jos käytät muuta kuin yksinkertaista tyyppiä, sen on myös tiedettävä kyseisen tyypin ’ rakenne, mutta osoittimet ovat usein samankokoisia ja
t on tärkeä prototyypin ’ s käytössä.
Vastaa
Jotain FILE*
-tyyppistä ei oikeastaan osoita rakennetta asiakaskoodin osalta, vaan on sen sijaan läpinäkymätön tunniste, joka liittyy joihinkin muu entiteetti, kuten tiedosto. Kun ohjelma kutsuu fopen
, se ei yleensä välitä palautetun rakenteen sisällöstä – kaikki mitä se välittää, on että muut toiminnot, kuten fread
, tekevät mitä tarvitsee tehdä sen kanssa.
Jos tavallinen kirjasto pitää FILE*
-tietoja esimerkiksi Tiedoston nykyisen lukupaikan kutsun fread
pitäisi pystyä päivittämään kyseiset tiedot. Kun fread
vastaanotetaan osoitin FILE
-palveluun, tämä on helppoa. Jos fread
sai sen sijaan FILE
, sillä ei ole mitään tapaa päivittää objektia FILE
soittajan hallussa.
Vastaa
Tiedot piilossa
Mikä on etun palautusosoitin rakenteeseen verrattuna koko rakenteen palauttamiseen toiminto?
Yleisin on tietojen piilottaminen . C: llä ei esimerkiksi ole kykyä tehdä struct
-kenttiä yksityisiksi, puhumattakaan siitä, että tarjoaa tapoja käyttää niitä.
Joten jos haluat pakottaa estä kehittäjiä näkemästä ja muokkaamasta osoitettavan sisältöä, kuten FILE
, silloin ainoa tapa on estää heitä altistamasta määritelmälle käsittelemällä osoitinta läpinäkymättömänä, jonka kohdepuolen kokoa ja määritelmää ulkomaailma ei tiedä. Määritelmä FILE
näkyy tällöin vain niille, jotka toteuttavat sen määrittelemistä vaativat toiminnot, kuten fopen
, mutta vain rakennedeklarointi näkyy julkisessa otsikossa.
Binaarinen yhteensopivuus
Rakennemäärityksen piilottaminen voi myös auttaa tarjoamaan hengitystilaa binäärisen yhteensopivuuden säilyttämiseksi dylib-sovellusliittymissä. Sen avulla kirjaston toteuttajat voivat muuttaa läpinäkymättömän rakenteen kenttiä ura rikkomatta binaarista yhteensopivuutta kirjastoa käyttävien kanssa, koska heidän koodinsa luonteen on tiedettävä vain, mitä he voivat tehdä rakenteella, ei kuinka suuri se on tai mitä kenttiä sillä on.
Esimerkiksi voin itse suorittaa joitain muinaisia ohjelmia, jotka on rakennettu Windows 95 -kauden aikana (ei aina täydellisesti, mutta yllättävän monet toimivat edelleen). Mahdollisuudet ovat, että osa näiden muinaisten binaarien koodista käytti läpinäkymättömiä viitteitä rakenteisiin, joiden koko ja sisältö ovat muuttuneet Windows 95: n aikakaudesta. Ohjelmat kuitenkin työskentelevät edelleen uusissa Windows-versioissa, koska ne eivät altistu näiden rakenteiden sisällölle. Kun työskentelet kirjastossa, jossa binaarinen yhteensopivuus on tärkeää, asiakkaan ei yleensä saa muuttua rikkomatta taaksepäin yhteensopivuus.
Tehokkuus
NULL-koko rakenteen palauttaminen on luultavasti vaikeampi tai vähemmän tehokasta. Onko tämä pätevä syy?
Tyypillisesti vähemmän tehokas olettaen, että tyyppi voidaan käytännössä sovittaa ja allokoida pinolle, ellei niitä ole tyypillisesti paljon vähemmän yleistettyä muistinjakolaitetta käytetään kulissien takana kuin malloc
, kuten kiinteän koon eikä muuttuvakokoisen allokoijan yhdistämämuisti, joka on jo varattu. Se on tässä tapauksessa turvallisuuden kompromissi. todennäköisesti antaa kirjaston kehittäjien ylläpitää FILE
-ohjelmaan liittyviä invarianteja (käsitteellisiä takuita).
Se ei ole niin pätevä syy ainakin suorituskyvyn kannalta saada fopen
palauttamaan osoitin, koska ainoa syy, miksi se ”palauttaa NULL
, on tiedoston avaamisen epäonnistuminen. Se olisi poikkeuksellisen skenaarion optimointi vastineeksi kaikkien yleisten tapausten toteutuspolkujen hidastamisesta. Joissakin tapauksissa voi olla pätevä tuottavuuteen liittyvä syy tehdä suunnittelusta yksinkertaisempi, jotta niistä tulisi paluuosoittimia, jotta NULL
voidaan palauttaa joissakin jälkisehdoissa.
Tiedostotoiminnoissa yleiskustannukset ovat suhteellisen melko triviaalit itse tiedostotoimintoihin verrattuna, eikä manuaalista tarvetta fclose
voida joka tapauksessa välttää. Joten ei ole, että voimme säästää asiakkaalta resurssin vapauttamisen (sulkemisen) vaivaa paljastamalla FILE
-määrityksen ja palauttamalla sen arvolla arvoon fopen
tai odota suurta tehokkuuden lisäystä, kun otetaan huomioon itse tiedostotoimintojen suhteelliset kustannukset, jotta vältetään kasan kohdentaminen.
Hotspotit ja korjaukset
Muissa tapauksissa olen kuitenkin profiloinut paljon tuhlaavaa C-koodia vanhoissa kooditukiasemissa, joissa on hotspotit malloc
ja tarpeettomat pakolliset välimuistihäviöt seurauksena tämän käytännön liian usein käyttämisestä läpinäkymättömien osoittimien kanssa ja liian monien asioiden jakamisesta tarpeettomasti kasaan, joskus isoissa silmukoissa.
Vaihtoehtona käytän sen sijaan rakennemääritelmien paljastamista, vaikka asiakkaan ei olisikaan tarkoitus muuttaa niitä, nimeämiskäytäntöstandardin avulla ilmoittamaan, ettei kukaan muu saa koskettaa kenttiä:
struct Foo { /* priv_* indicates that you shouldn"t tamper with these fields! */ int priv_internal_field; int priv_other_one; }; struct Foo foo_create(void); void foo_destroy(struct Foo* foo); void foo_something(struct Foo* foo);
Jos tulevaisuudessa on binaarien yhteensopivuusongelmia, olen huomannut sen olevan tarpeeksi hyvä varaamaan vain tarpeettomasti ylimääräistä tilaa tulevaisuuden tarkoituksiin, kuten:
struct Foo { /* priv_* indicates that you shouldn"t tamper with these fields! */ int priv_internal_field; int priv_other_one; /* reserved for possible future uses (emergency backup plan). currently just set to null. */ void* priv_reserved; };
Tämä varattu tila on hieman tuhlaavainen, mutta voi olla elämän säästö, jos havaitsemme tulevaisuudessa, että meidän on lisättävä lisää tietoja Foo
rikkomatta kirjastoa käyttäviä binäärejä.
Mielestäni tietojen piilottaminen ja binäärinen yhteensopivuus ovat tyypillisesti ainoa hyvä syy sallia vain kasojen allokointi rakenteet muuttuvan pituisten rakenteiden lisäksi (mikä vaatii aina sitä tai ainakin on vähän hankalaa käyttää muuten, jos asiakkaan on allokoitava muistia pinossa VLA-fashissa VLS: n varaamiseksi). Jopa suurten osien palauttaminen on usein halvempaa, jos se tarkoittaa, että ohjelmisto työskentelee paljon enemmän pinon kuuman muistin kanssa. Ja vaikka niiden palauttaminen ei olisikaan edullisempaa arvon perusteella, voisi yksinkertaisesti tehdä tämän:
int foo_create(struct Foo* foo); ... /* In the client code: */ struct Foo foo; if (foo_create(&foo)) { foo_something(&foo); foo_destroy(&foo); }
… alustaa Foo
pinosta ilman tarpeetonta kopiota. Tai asiakkaalla on jopa vapaus kohdistaa kasaan Foo
, jos he jostain syystä haluavat.
gets()
-toiminto poistettiin. Joillakin ohjelmoijilla on edelleen vastenmielisyys rakenteiden kopioimisesta, vanhat tavat kuolevat kovasti.FILE*
on käytännössä läpinäkymätön kahva. Käyttäjäkoodin ei pitäisi välittää sen sisäisestä rakenteesta.&
ja käyttää jäsentä, jolla on.
. ”