Funkcje zwracające ciągi, dobry styl?

11

W moich programach C często potrzebuję sposobu, aby utworzyć ciąg reprezentujący moje ADT. Nawet jeśli nie muszę w żaden sposób drukować łańcucha na ekranie, fajnie jest mieć taką metodę debugowania. Tak więc często pojawia się tego rodzaju funkcja.

char * mytype_to_string( const mytype_t *t );

W rzeczywistości zdaję sobie sprawę, że mam (przynajmniej) trzy opcje obsługi pamięci dla ciągu do zwrócenia.

Alternatywa 1: Przechowywanie łańcucha zwrotnego w statycznej tablicy znaków w funkcji. Nie potrzebuję dużo myślenia, poza tym, że ciąg jest nadpisywany przy każdym wywołaniu. Co może być problemem w niektórych przypadkach.

Alternatywa 2: Przydziel ciąg na stercie z malloc wewnątrz funkcji. Naprawdę fajnie, bo wtedy nie będę musiał myśleć o wielkości bufora ani o nadpisywaniu. Jednak muszę pamiętać, aby po zakończeniu wykonać polecenie free (), a następnie muszę także przypisać zmienną tymczasową, którą mogę zwolnić. a następnie przydzielanie sterty jest naprawdę znacznie wolniejsze niż przydzielanie stosu, dlatego bądź wąskim gardłem, jeśli powtarza się to w pętli.

Alternatywa 3: Przekaż wskaźnik do bufora i pozwól programowi wywołującemu przydzielić ten bufor. Lubić:

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

Daje to więcej wysiłku dzwoniącemu. Zauważam również, że ta alternatywa daje mi inną opcję w kolejności argumentów. Który argument powinienem mieć pierwszy i ostatni? (właściwie sześć możliwości)

Które więc powinienem preferować? Dlaczego? Czy istnieje jakiś niepisany standard wśród programistów C?

Øystein Schønning-Johansen
źródło
3
Nota obserwacyjna: większość systemów operacyjnych korzysta z opcji 3 - dzwoniący i tak przydziela bufor; informuje wskaźnik bufora i pojemność; odbiorca wypełnia bufor, a także zwraca faktyczną długość łańcucha, jeśli bufor jest niewystarczający. Przykład: sysctlbynamew OS X i iOS
rwong

Odpowiedzi:

11

Metody, które widziałem najczęściej to 2 i 3.

Bufor dostarczony przez użytkownika jest w rzeczywistości dość prosty w użyciu:

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

Chociaż większość implementacji zwróci ilość użytego bufora.

Opcja 2 będzie wolniejsza i jest niebezpieczna przy korzystaniu z bibliotek połączonych dynamicznie, w których mogą używać różnych środowisk wykonawczych (i różnych stosów). Nie możesz więc uwolnić tego, co zostało mallocedowane w innej bibliotece. To wymaga free_string(char*)funkcji, która sobie z tym poradzi.

maniak zapadkowy
źródło
Dzięki! Myślę, że najbardziej podoba mi się również Alternative 3. Jednak chcę mieć możliwość robienia takich rzeczy: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));i dlatego nie chcę zwracać użytej długości, ale wskaźnik do łańcucha. Komentarz biblioteki dynamicznej jest naprawdę ważny.
Øystein Schønning-Johansen
Czy nie powinno to sizeof(buffer) - 1zadowolić \0terminatora?
Michael-O,
1
@ Michael-O nie termin pusty jest uwzględniony w rozmiarze bufora, co oznacza, że ​​maksymalny ciąg, który można wstawić, jest o 1 mniejszy niż przekazany rozmiar. Jest to wzorzec działania bezpiecznego łańcucha w standardowej bibliotece, np snprintf. Use.
maniak zapadkowy
@ratchetfreak Dzięki za wyjaśnienie. Byłoby miło rozszerzyć tę odpowiedź z tą mądrością.
Michael-O,
0

Dodatkowy pomysł na projekt # 3

Jeśli to możliwe, podaj również maksymalny wymagany rozmiar mytypew tym samym pliku .h co mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256

Teraz użytkownik może odpowiednio kodować.

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

Zamówienie

Rozmiar tablic, gdy jest pierwszy, pozwala na typy VLA.

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

Nie tak ważne z jednym wymiarem, ale przydatne z 2 lub więcej.

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

Pamiętam, że czytanie pierwszego rozmiaru jest preferowanym idiomem w następnym C. Muszę znaleźć to odniesienie ...

chux - Przywróć Monikę
źródło
0

Jako dodatek do doskonałej odpowiedzi @ ratchetfreak wskazałbym, że alternatywa # 3 ma podobny paradygmat / wzorzec jak standardowe funkcje biblioteki C.

Na przykład strncpy.

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

Przestrzeganie tego samego paradygmatu pomogłoby zmniejszyć obciążenie poznawcze dla nowych programistów (a nawet przyszłego siebie), gdy będą musieli korzystać z Twojej funkcji.

Jedyną różnicą w stosunku do tego, co masz w poście, jest to, że destinationargument w bibliotekach C zwykle jest wymieniony jako pierwszy na liście argumentów. Więc:

char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen ); 
John Go-Soco
źródło
-2

Poza tym, że to, co proponujesz, to nieprzyjemny zapach kodu, alternatywne 3 brzmią dla mnie najlepiej. Myślę też, jak @ gnasher729, że używasz niewłaściwego języka.

John Pfersich
źródło
Co dokładnie uważasz za zapach kodu? Proszę opracować.
Hulk
-3

Szczerze mówiąc, możesz przejść na inny język, w którym zwracanie ciągu nie jest skomplikowaną, pracochłonną i podatną na błędy operacją.

Możesz rozważyć C ++ lub Objective-C, w którym możesz pozostawić 99% kodu bez zmian.

gnasher729
źródło