Toiminnot, jotka palauttavat jouset, hyvä tyyli?

C-ohjelmissani tarvitsen usein tapaa tehdä merkkijonoesitys ADT-tiedostoistani. Vaikka minun ei tarvitse tulostaa merkkijonoa näytettäväksi millään tavalla, on siisti olla tällainen menetelmä virheenkorjaukseen. Joten tällainen toiminto tulee usein esiin.

char * mytype_to_string( const mytype_t *t ); 

Ymmärrän itse asiassa, että minulla on täällä (ainakin) kolme vaihtoehtoa käsitelläksesi muistia merkkijonon palauttamiseksi.

Vaihtoehto 1: Palautusmerkkijonon tallentaminen funktion staattiseen char-ryhmään En tarvitse paljon ajattelua, paitsi että merkkijono korvataan jokaisessa puhelussa. Mikä voi olla ongelma joissakin tilanteissa.

Vaihtoehto 2: Kohdista kasan merkkijono mallocilla funktion sisällä. Todella siisti, koska minun ei silloin tarvinnut ajatella puskurin kokoa tai päällekirjoitusta. Minun täytyy kuitenkin muistaa vapauttaa merkkijono (), kun olen valmis, ja sitten minun on määritettävä myös väliaikainen muuttuja siten, että Voin vapauttaa. Ja sitten kasan allokointi on todella paljon hitaampaa kuin pinon allokointi, joten ole pullonkaula, jos tämä toistetaan silmukassa.

Vaihtoehto 3: Siirrä osoitin puskuriin ja anna soittajan kohdistaa puskuri. Kuten:

char * mytype_to_string( const mytype_t *mt, char *buf, size_t buflen ); 

Tämä tuo lisää vaivaa soittajalle. Huomaan myös, että tämä vaihtoehto antaa minulle toisen vaihtoehdon argumenttien järjestyksessä. Mikä argumentti minulla pitäisi olla ensimmäinen ja viimeinen? (Oikeastaan kuusi mahdollisuutta)

Joten kumpi pitäisi mieluummin? Miksi miksi? Onko C-kehittäjissä jonkinlainen kirjoittamaton standardi?

Kommentit

  • Vain havainnointihuomautus, useimmat käyttöjärjestelmät käyttävät vaihtoehtoa 3 – soittaja varaa puskurin joka tapauksessa; kertoo puskuriosoittimen ja kapasiteetin; soittaja täyttää buffin er ja palauttaa myös merkkijonon todellisen pituuden , jos puskuri ei ole riittävä. Esimerkki: sysctlbyname OS X: ssä ja iOS: ssä

Vastaus

Eniten nähneet menetelmät ovat 2 ja 3.

Käyttäjän toimittama puskuri on itse asiassa melko yksinkertainen käyttää:

char[128] buffer; mytype_to_string(mt, buffer, 128); 

Vaikka useimmat toteutukset palauttavat käytetyn puskurin määrän.

Vaihtoehto 2 on hitaampi ja vaarallinen käytettäessä dynaamisesti linkitettyjä kirjastoja, joissa ne saattavat käyttää erilaisia ajonaikoja (ja erilaisia kasoja). Joten et voi vapauttaa sitä, mikä on malloced toisessa kirjastossa. Tämä edellyttää sitten free_string(char*) -toimintoa sen käsittelemiseksi.

Kommentit

  • Kiitos! Luulen, että pidän myös vaihtoehdosta 3 parhaiten. Haluan kuitenkin pystyä tekemään esimerkiksi seuraavia asioita: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf)); ja näin ollen olen voittanut ’ haluan palauttaa käytetyn pituuden vaan osoittimen merkkijonoon. Dynaaminen kirjastokommentti on todella tärkeä.
  • Pitäisi ’ t olla sizeof(buffer) - 1, jotta \0 terminaattori?
  • @ Michael-O no null-termi ei sisälly puskurikokoon, mikä tarkoittaa, että suurin mahdollinen merkkijono on 1 pienempi kuin läpäistyn kokoinen. Tätä mallia turvallinen merkkijono toimii vakiokirjastossa, kuten snprintf.
  • @ratchetfreak Kiitos selvityksestä. Olisi mukavaa laajentaa vastausta tällä viisaudella.

Vastaa

# 3: n lisäidea

Anna mahdollisuuksien mukaan myös mytype samassa .h-tiedostossa kuin mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256 

Nyt käyttäjä voi koodata vastaavasti.

char buf[MYTYPE_TO_STRING_SIZE]; puts(mytype_to_string(mt, buf, sizeof buf)); 

Tilaa

Ensimmäisen kerran matriisien koko sallii VLA-tyypit.

char * mytype_to_string( const mytype_t *mt, size_t bufsize, char *buf[bufsize]); 

Ei niin tärkeä yhdellä ulottuvuudella, mutta hyödyllinen vähintään 2: lla.

void matrix(size_t row, size_t col, double matrix[row][col]); 

Muistan, että ensi koon lukeminen on suositeltava sanamuoto seuraavassa C. On löydettävä viite ….

Vastaa

@ratchetfreakin erinomaisen vastauksen lisäksi haluaisin huomauttaa, että vaihtoehto # 3 noudattaa samanlaista paradigmaa / mallia kuin tavalliset C-kirjastotoiminnot.

Esimerkiksi strncpy.

 char * strncpy ( char * destination, const char * source, size_t num );  

Saman paradigman noudattaminen auttaisi vähentää uusien kehittäjien (tai jopa tulevan itsesi) kognitiivista kuormitusta, kun heidän on käytettävä toimintoasi.

Ainoa ero viestissäsi olevaan sisältöön on, että destination-argumentti C-kirjastoissa on yleensä lueteltu ensimmäisenä argumenttiluettelossa.Joten:

 char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen );  

vastaus

I echo @ratchet_freak useimmissa tapauksissa (ehkä pienellä säätimellä sizeof buffer yli 128) mutta haluan hypätä tänne outoon vastaukseen. Entä outo oleminen? Miksi ei, sen lisäksi, että kollegojemme outo ulkonäkö ja ylimääräinen suostuttelu ovat ongelmia? Ja tarjoan tämän:

// Note allocator parameter. char* mytype_to_string(allocator* alloc, const mytype_t* t) { char* buf = allocate(alloc, however_much_you_need); // fill out buf based on "t" contents return buf; } 

Ja esimerkkikäyttö:

void func(my_type a, my_type b) { allocator alloc = allocator_new(); const char* str1 = mytype_to_string(&alloc, &a); if (!str1) goto oom; const char* str2 = mytype_to_string(&alloc, &b); if (!str2) goto oom // do something with str1 and str2 goto finish; oom: errno = ENOMEM; finish: // Frees all memory allocated through `alloc`. allocator_purge(&alloc); } 

Jos teit näin, voit tehdä allokaattoristasi erittäin tehokkaan (tehokkaamman) kuin malloc sekä allokaatio- / jakaumakustannusten että parannetun muistipaikkaviitteiden sijainnin suhteen.) Se voi olla areena-allokaattori, joka vain lisää osoitinta tavallisissa tapauksissa allokointipyyntöihin ja yhdistää muistia peräkkäin suurista vierekkäisistä lohkoista (jolloin ensimmäinen lohko ei edes vaadi kasaa al sijainti – se voidaan kohdistaa pinoon). Se yksinkertaistaa virheiden käsittelyä. Lisäksi, ja tämä voi olla eniten kiistanalaisinta, mutta mielestäni se on käytännössä melko ilmeistä siitä, miten se tekee soittajalle selväksi, että se aikoo allokoida muistia jakamallesi jakajalle, mikä vaatii nimenomaista vapauttamista (allocator_purge tässä tapauksessa) tarvitsematta dokumentoida tällaista käyttäytymistä kaikissa mahdollisissa funktioissa, jos käytät tätä tyyliä johdonmukaisesti. Aloittimen parametrin hyväksyminen tekee siitä toivottavasti melko ilmeisen.

En tiedä. Saan täällä vastalauseita, kuten mahdollisimman yksinkertaisen mahdollisen areenan allokaattorin toteuttaminen (käytä vain suurinta kohdistusta kaikkiin pyyntöihin) ja sen käsitteleminen on liikaa työtä. Suora ajatukseni on, mitä me olemme, Python-ohjelmoijat? Voisiko myös käyttää Pythonia, jos niin. Käytä Pythonia, jos näillä yksityiskohdilla ei ole väliä. Olen tosissani. Minulla on ollut monia C-ohjelmointikollegoita, jotka todennäköisesti kirjoittavat paitsi oikeamman koodin, mutta mahdollisesti jopa tehokkaammin Pythonin kanssa, koska he jättävät huomiotta esimerkiksi viitepaikkakunnan ja kompastuvat vasemmalle ja oikealle luomiin virheisiin. Näemme, mikä on niin pelottavaa jostakin yksinkertaisesta areenaallokaattorista, jos olemme C-ohjelmoijia, jotka ovat kiinnostuneita asioista, kuten datan sijainti ja optimaalinen käskyvalinta, ja tämä on kiistatta paljon vähemmän ajateltavaa kuin ainakin sellaiset liitännät, jotka edellyttävät soittajat vapauttavat nimenomaisesti kaikki yksittäiset asiat, jotka käyttöliittymä voi palauttaa. Se tarjoaa joukkojakauman, joka on virheellisempi. Oikea C-ohjelmoija haastaa silmukkakutsuja malloc, kuten näen sen, varsinkin kun se näkyy hotspotina heidän profilerissaan. Minun mielestäni ” oomph ” on oltava enemmän perusteluja siitä, että olemme edelleen C-ohjelmoija vuonna 2020, ja voimme ” älä enää pidä irti muista asioista, kuten muistinjakolaitteista.

Ja tällä ei ole reunatapauksia varata kiinteän kokoinen puskuri, jossa varataan esimerkiksi 256 tavua ja tuloksena oleva merkkijono on suurempi. Vaikka funktiomme välttäisivät puskurin ylityksiä (kuten sprintf_s), on enemmän koodia palautua asianmukaisesti sellaisista vaadituista virheistä, jotka voimme jättää pois kohdistimen tapauksessa, koska se ei ”Minulla ei ole näitä reunatapauksia. Meidän ei tarvitse käsitellä tällaisia tapauksia allokaattoritapauksessa, ellei me todella tyhjennä laitteistomme fyysistä osoitetilaa (jota yllä oleva koodi käsittelee, mutta sen ei tarvitse käsitellä ” ennalta varatusta puskurista ” erillään ” muistista ”).

Vastaa

Sen lisäksi, että ehdotat tehdä huono koodihaju, vaihtoehto 3. kuulostaa minulle parhaiten. Luulen myös, että kuten @ gnasher729, että käytät väärää kieltä.

Kommentit

Vastaus

Ollakseni rehellinen, kannattaa ehkä vaihtaa toiseen kieleen, jossa merkkijonon palauttaminen ei ole monimutkainen, paljon työtä vaativa ja virhealtista toimintaa.

Voisit harkita C ++ tai Objective-C, joissa voit jättää 99% koodistasi muuttumattomaksi.

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *