2011-12-22 16 views
14

Mam funkcję, która ma dwie liczby całkowite i zwraca ciąg znaków. Teraz mam to:Poprawny sposób zwraca ciąg znaków z funkcji

char* myfunc(int a, int b, int* len) 
{ 
    int retLen = ... 
    char* ret = malloc(retLen + 1); 

    if (len != NULL) 
    { 
     *len = retLen; 
    } 

    return ret; 
} 

Jednak większość funkcji w bibliotece C mają tendencję do zrobienia czegoś więcej tak:

int myfunc(char* ret, int a, int b) 
{ 
    ... 

    return retLen; 
} 

następnie spodziewa się, aby przydzielić pamięć dla funkcji do wypełnienia. To pozwala ci zrobić trochę więcej, jak wybrać, gdzie jest przydzielony ciąg.

W tym przypadku jest kilka matematyki wymaganych w funkcji, aby uzyskać długość i nie ma powodu, aby mieć bufor o dowolnym rozmiarze innym niż potrzebny. Nie ma górnego limitu rozmiaru bufora (i tak nie jest rozsądny).

Co jest uważane za dobrą praktykę podczas zwracania ciągu, którego długość jest dynamicznie znaleziona, biorąc pod uwagę dane wejściowe?

Odpowiedz

7

Wzór widziałem w programach trybu jądra jest:

  1. wywołać funkcję kiedyś z pewnym przydzielonej pamięci, jeśli zdarzy się, że niektóre dostępne, lub null jako param jeśli zdarzy się, że none
  2. Jeśli masz przydzieloną pamięć i funkcja znalazła go wystarczająco, umieszcza wynik w tej pamięci i zwraca OK
  3. Jeśli nie masz pamięci do przekazania lub pamięć przeszła za mało, funkcja zwraca ERROR_NOT_ENOUGH_MEMORY i umieszcza w parametrze wyjściowym potrzebna pamięć.
    • Następnie przydzielić tej potrzebnej pamięci i wywołać funkcję ponownie

Próbka:

int myfunc(
    __out char* output, 
    __in size_t given, 
    __out size_t needed_or_resulted, 
    extra params ... 
){ 
    ... implementation 
} 

needed_or_resulted może być również używany do przesyłania ile danego pamięci użyto przypadek sukcesu.

Do zastosowania, takie jak:

int result = myfunc(output, given, needed_or_resulted, extra params ...); 
if(result == OK) { 
    // all ok, do what you need done with result of size "needed_or_resulted" on "output" 
} else if(result == ERROR_NOT_ENOUGH_MEMORY) { 
    output = malloc(needed ... 
    result = myfunc(output, given, needed_or_resulted, extra params ...); 
    if(result == OK) { 
     // all ok, do what you need done with result of size "needed_or_resulted" on "output" 
    } else if(result == ERROR_OTHER) { 
     // handle other possible errors 
    } else { 
     // handle unknown error 
    } 
} else if(result == ERROR_OTHER) { 
    // handle other possible errors 
} else { 
    // handle unknown error 
} 
+0

Oczywiście, z tym wzorem, przekazałbyś długość wraz ze wskaźnikiem. Może to być nawet parametr wejściowy/wyjściowy (przekazywany jako 'size_t *'). – cHao

+0

Tak, rzeczywiście, zredagowałem podpis. – clyfe

+0

Bardzo dobra odpowiedź, która bardzo pomaga, więc dziękuję. Kilka pytań: Dlaczego jest to preferowane do funkcji takiej jak 'size_t myfunc (char * output, size_t given, ...)' która skutecznie zwraca 'needed_or_resulted', aby użytkownik mógł ją porównać, aby uzyskać własne znaczenie sukcesu? Dlaczego jest to preferowane dla drugiej funkcji, np. 'MyfuncLength', która wykonuje niezbędne obliczenia długości? – Matt

2

Masz rację co do przyczyny, dla której preferowany jest podpis int myfunc(char* ret, int a, int b). To wyjaśnia inną rzecz - dlaczego należy zwrócić długość (bufor ma rozmiar MAX, więc zwykle musimy poinformować dzwoniącego o tym, ile faktycznie wykorzystaliśmy).

Po przypisaniu łańcuchów do funkcji zwykle nie zwraca się rozmiaru łańcucha, ponieważ można go znaleźć za pomocą strlen. Spójrz na strdup dla przykładu funkcji, która dynamicznie przydziela ciągi. Więc chciałbym zmienić podpis swojej funkcji do

char* myfunc(int a, int b) { 
    ... 
} 
+0

Jeśli dobrze czytam pytanie, nie ma "MAX". Lub jeśli taki istnieje, jest zbyt duży, aby tworzyć bufory. – cHao

+0

@CHao Prawidłowo, jest to wyimaginowane "MAX", które OP mówi, że nie chce wprowadzić. W pierwszej części odpowiedzi wyjaśniam, dlaczego musimy zwracać długość, gdy występuje "MAX", nie sugerując, że OP powinien mieć jeden. Druga część dotyczy bardziej sytuacji. – dasblinkenlight

+0

Ponadto, ten rodzaj zmienia regułę o 'free'ing co masz' malloc' na uchu. Lepiej wyjaśnij, kto jest właścicielem pamięci w dokumentacji tej funkcji. – cHao

3

Ten ostatni jest lepszy, ponieważ wskazówki w rozmówcy o tym, kto jest odpowiedzialny za uwolnienie pamięci. Ten pierwszy powoduje duże problemy, jeśli rozmówca i osoba korzystająca z różnych implementacji malloc (np. W systemie Windows, debugowaniu i wydaniu często używają niekompatybilnych modeli pamięci).

1

ja przechodzą char * przez odniesienie.Jeśli funkcja działa poprawnie, przydziel ciąg znaków i przypisz go do referencji wskaźnika i zwróć długość łańcucha. Jeśli wystąpi błąd, ustaw errno i zwróć -1.

int myfunc(int a, int b, char ** str) 
{ 
    int retLen; 

    /* code to calculate string length required */ 

    if (!(str)) 
    { 
     errno = EINVAL; 
     return(-1); 
    }; 
    if (!(*str = malloc(retLen))) 
     return(-1); 

    /* calculate new value and store to string */ 

    return(retLen); 
} 
2

Śledź interfejs snprintf, standardowych funkcji z dokładnie tego samego problemu:

size_t myfunc(char *s, size_t n, int a, int b); 

wyjścia bajtów za n-1 należy odrzucić zamiast napisane na tablicy, a bajt zerowy jest zapisywany na końcu bajtów faktycznie zapisanych w tablicy.

Po pomyślnym zakończeniu funkcja snprintf() zwraca liczbę bajtów , które zostałyby zapisane na s, które były wystarczająco duże z wyłączeniem kończącego znaku null> bajtu.

Jeżeli wartość n wynosi zero na wezwanie do snprintf(), nic nie będzie napisane, liczba bajtów, które zostały napisane że brak było wystarczająco duża wyłączeniem NULL kończącego zostaną zwrócone, i s może być wskaźnikiem zerowym.

Typowe zastosowanie:

size_t needed = myfunc(0, 0, a, b) + 1; 
char *buf = malloc(needed); 
if (buf) { 
    myfunc(buf, needed, a, b); 
} 

Można zawierać nul bajt w liczbie wrócił - to sprawia, że ​​kod wywołujący prostsze, choć nieco mniej znane ludziom stosowanych do standardowego snprintf.

Jeśli obliczanie wartości retLen jest zaskakująco kosztowne, może istnieć argument dotyczący funkcji, która oblicza ją podczas generowania ciągu znaków, i zwraca przydzielony bufor o odpowiednim rozmiarze (być może po przeprojektowaniu realloc). Ale zwykle nie myślałem o tym. Dla wygody użytkowników, którzy chcą dokonać alokacji, po prostu umieść powyższy kod w funkcji myfunc_alloc i nie przejmujcie się, że to duplikuje trochę pracy. Użytkownicy, którzy już mają bufor, mogą zadzwonić pod numer: myfunc bezpośrednio w następujący sposób:

if (myfunc(buf, bufsize, a, b) >= bufsize) { 
    printf("buffer too small, string (%s) has been truncated\n", buf); 
} 
Powiązane problemy