Funkce vracející řetězce, dobrý styl?

Ve svých programech C často potřebuji způsob, jak vytvořit řetězcovou reprezentaci mých ADT. I když nemusím řetězec na obrazovku jakýmkoli způsobem tisknout, je hezké mít takový způsob ladění. Takže tento druh funkce často přichází.

char * mytype_to_string( const mytype_t *t ); 

Vlastně si uvědomuji, že zde mám (alespoň) tři možnosti pro zpracování paměti pro návrat řetězce.

Alternativa 1: Uložení návratového řetězce do statického char pole ve funkci Nepotřebuji moc přemýšlet, až na to, že řetězec je přepsán při každém hovoru. Což může být při některých příležitostech problém.

Alternativa 2: Přiřaďte řetězec na haldě s malloc uvnitř funkce. Opravdu čistý, protože jsem potom nemusel myslet na velikost vyrovnávací paměti nebo přepsání. Musím si však pamatovat uvolnění () řetězce, když je hotový, a pak musím také přiřadit dočasnou proměnnou tak, že Mohu uvolnit. A pak je alokace haldy opravdu mnohem pomalejší než alokace zásobníku, proto buďte úzkým hrdlem, pokud se to opakuje ve smyčce.

Alternativa 3: Předejte ukazatel na vyrovnávací paměť a nechte volajícího přidělit jako:

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

To přináší volajícímu větší úsilí. Všiml jsem si také, že tato alternativa mi dává jinou možnost v pořadí argumentů. Který argument bych měl mít první a poslední? (Ve skutečnosti šest možností)

Takže, kterému mám dát přednost? Nějaký důvod? Existuje nějaký nepopsaný standard mezi vývojáři jazyka C?

Komentáře

  • Pouze jako pozorovací poznámka, většina operačních systémů používá možnost 3 – volající přiděluje vyrovnávací paměť v každém případě; řekne ukazatel a kapacitu vyrovnávací paměti; volaný vyplní buff er a také vrátí skutečnou délku řetězce , pokud je vyrovnávací paměť nedostatečná. Příklad: sysctlbyname v OS X a iOS

Odpověď

Metody, které jsem viděl nejvíce, jsou 2 a 3.

Vyrovnávací paměť dodaná uživatelem je ve skutečnosti velmi jednoduchá:

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

Ačkoli většina implementací vrátí použité množství vyrovnávací paměti.

Možnost 2 bude pomalejší a je nebezpečná při použití dynamicky propojených knihoven, kde je lze použít různé doby běhu (a různé hromady). Takže nemůžete uvolnit to, co bylo zneužito v jiné knihovně. K tomu je potom potřeba funkce free_string(char*), která se s tím vypořádá.

Komentáře

  • Děkujeme! Myslím, že mám nejraději také Alternativu 3. Chci však být schopen dělat věci jako: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));, a proto nebudu ‚ chtít vrátit použitou délku, ale spíše ukazatel do řetězce. Komentář dynamické knihovny je opravdu důležitý.
  • Nemělo by to být ‚ sizeof(buffer) - 1 k uspokojení \0 terminátor?
  • @ Michael-O ne, do velikosti vyrovnávací paměti je zahrnut nulový výraz, což znamená, že maximální řetězec, který lze vložit, je o 1 menší než předaná velikost. Toto je vzor, který bezpečný řetězec funguje ve standardní knihovně jako snprintf.
  • @ratchetfreak Děkujeme za vysvětlení. Bylo by hezké rozšířit odpověď o tuto moudrost.

Odpověď

Další návrhový návrh pro # 3

Je-li to možné, uveďte také maximální velikost potřebnou pro mytype ve stejném souboru .h jako mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256 

Nyní může uživatel odpovídajícím způsobem kódovat.

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

Objednávka

Velikost polí, je-li první, umožňuje typy VLA.

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

Není to tak důležité pro jednu dimenzi, ale užitečné pro 2 nebo více.

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

Vzpomínám si, že čtení s velikostí jako první je preferovaný idiom v příštím C. Je třeba najít tento odkaz ….

Odpověď

Jako doplněk k vynikající odpovědi @ratchetfreak bych chtěl poukázat na to, že alternativa # 3 sleduje podobné paradigma / vzor jako standardní funkce knihovny C.

Například strncpy.

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

Sledování stejného paradigmatu by pomohlo snížit kognitivní zátěž pro nové vývojáře (nebo dokonce vaše budoucí já), když potřebují použít vaši funkci.

Jediný rozdíl v tom, co ve svém příspěvku máte, by bylo v tom, že destination argument v knihovnách C má tendenci být uveden jako první v seznamu argumentů.Takže:

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

Odpovědět

Ve většině případů bych echo @ratchet_freak (možná s malým vylepšením pro sizeof buffer nad 128) ale chci sem skočit s podivnou odpovědí. Co takhle být divný? Proč ne, kromě problémů se získáváním podivných pohledů od našich kolegů a nutností být přesvědčivý? A nabízím toto:

// 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; } 

A příklad použití:

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); } 

Pokud jste to udělali tímto způsobem, můžete svůj alokátor velmi zefektivnit (efektivnější než malloc jak z hlediska nákladů na alokaci / deallokaci, tak i vylepšené lokality odkazu pro přístup do paměti). Může to být alokátor arény, který v běžných případech zahrnuje pouze zvýšení ukazatele pro žádosti o přidělení a paměť paměti sekvenčně z velkých souvislých bloků (přičemž první blok nevyžaduje ani haldu al umístění – lze jej přidělit na zásobníku). Zjednodušuje zpracování chyb. Také, a to by mohlo být nejvíce diskutabilní, ale myslím si, že je to prakticky zcela zřejmé, pokud jde o to, jak volajícímu dá jasně najevo, že přidělí paměť přidělovači, který předáte, vyžadující explicitní uvolnění (allocator_purge v tomto případě), aniž byste museli toto chování dokumentovat v každé možné funkci, pokud tento styl používáte důsledně. Díky přijetí parametru alokátoru je to snad zcela zřejmé.

Nevím. Zde dostávám protiargumenty, jako je implementace nejjednoduššího možného alokátoru arény (pro všechny požadavky použijte maximální zarovnání) a vypořádat se s tím je příliš mnoho práce. Moje tupá myšlenka zní jako, co jsme my, programátoři Pythonu? Mohl bych také použít Python, pokud ano. Použijte prosím Python, pokud na těchto detailech nezáleží. Myslím to vážně. Měl jsem mnoho kolegů z programování v C, kteří by s největší pravděpodobností psali nejen přesnější kód, ale možná ještě efektivnější s Pythonem, protože ignorují věci jako referenční lokalitu a narážejí na chyby, které vytvářejí vlevo a vpravo. Nechápu, co je na tom tak strašidelného na nějakém jednoduchém alokátoru arény, pokud jsme programátoři C, kteří se zabývají věcmi, jako je datová lokalita a optimální výběr instrukcí, a to je pravděpodobně mnohem méně na přemýšlení, než alespoň ty typy rozhraní, která vyžadují volající výslovně uvolnit každou jednotlivou věc, kterou může rozhraní vrátit. Nabízí hromadné uvolnění přes individuální uvolnění druhu, který je náchylnější k chybám. Správný programátor jazyka C zpochybňuje smyčková volání malloc, jak to vidím, zvláště když se v jejich profilovacím programu jeví jako hotspot. Z mého pohledu musí existovat více “ oomph “ důvodů, proč v roce 2020 stále být programátorem C, a my můžeme “ Už se nebudeme vyhýbat věcem, jako jsou alokátory paměti.

A to už nemá okrajové případy přidělení vyrovnávací paměti pevné velikosti, kde přidělíme, řekněme, 256 bajtů a výsledný řetězec je větší. I když naše funkce zabraňují přetečení vyrovnávací paměti (jako u sprintf_s), existuje více kódu pro správné zotavení z takových chyb, které jsou povinné a které můžeme v případě alokátoru vynechat, protože „Tyto okrajové případy nemáme. V případě alokátoru takové případy řešit nemusíme, pokud skutečně nevyčerpáme fyzický adresní prostor našeho hardwaru (který výše uvedený kód zpracovává, ale nemusí se s ním vypořádat “ z předem přidělené vyrovnávací paměti “ odděleně od “ z paměti „).

Odpověď

Kromě toho, že to, co navrhujete, je špatný zápach kódu, alternativa 3 mi zní nejlépe. Také si myslím, že jako @ gnasher729 používáte špatný jazyk.

Komentáře

Odpověď

Abych byl upřímný, možná budete chtít přepnout na jiný jazyk, kde vrácení řetězce není složitá operace náročná na práci a náchylnou k chybám.

Můžete zvážit C ++ nebo Objective-C, kde můžete nechat 99% svého kódu beze změny.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *