2013-06-12 6 views
8

Mam funkcję ciągu, który akceptuje wskaźnik do łańcucha źródłowego i zwraca wskaźnik do łańcucha docelowego. Ta funkcja obecnie działa, ale obawiam się, że nie stosuję się do najlepszych praktyk odnawiając malloc, realloc i za darmo.najlepsza praktyka zwracania zmiennej długości łańcucha w c

Rzeczą, która różni się w mojej funkcji jest to, że długość docelowego łańcucha nie jest taka sama jak łańcucha źródłowego, więc realloc() musi być wywołana w mojej funkcji. Wiem, że od patrzenia na docs ...

http://www.cplusplus.com/reference/cstdlib/realloc/

że adres pamięci może się zmienić po realloc. Oznacza to, że nie mogę "przekazać przez odniesienie", tak jak programista C może dla innych funkcji, muszę zwrócić nowy wskaźnik.

więc prototypem mojej funkcji jest następująca:

//decode a uri encoded string 
char *net_uri_to_text(char *); 

nie podoba mi się sposób, w jaki ja to robię, bo muszę uwolnić wskaźnik po uruchomieniu funkcji:

char * chr_output = net_uri_to_text("testing123%5a%5b%5cabc"); 
printf("%s\n", chr_output); //testing123Z[\abc 
free(chr_output); 

Co oznacza, że ​​malloc() i realloc() są wywoływane wewnątrz mojej funkcji, a free() jest wywoływana poza moją funkcją.

Mam doświadczenie w językach wysokiego poziomu (Perl, plpgsql, bash), więc mój instynkt jest właściwy hermetyzacji o takich rzeczach, ale to może nie być najlepsze praktyki w C

na pytanie: Czy mój najlepsza praktyka, czy jest lepszy sposób, w jaki powinienem podążać?

pełny przykład

kompiluje i uruchamia dwa ostrzeżenia na niewykorzystane argc i argv argumentów, można zignorować te dwa ostrzeżenia.

przyklad.c:

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 

char *net_uri_to_text(char *); 

int main(int argc, char ** argv) { 
    char * chr_input = "testing123%5a%5b%5cabc"; 
    char * chr_output = net_uri_to_text(chr_input); 
    printf("%s\n", chr_output); 
    free(chr_output); 
    return 0; 
} 

//decodes uri-encoded string 
//send pointer to source string 
//return pointer to destination string 
//WARNING!! YOU MUST USE free(chr_result) AFTER YOU'RE DONE WITH IT OR YOU WILL GET A MEMORY LEAK! 
char *net_uri_to_text(char * chr_input) { 
    //define variables 
    int int_length = strlen(chr_input); 
    int int_new_length = int_length; 
    char * chr_output = malloc(int_length); 
    char * chr_output_working = chr_output; 
    char * chr_input_working = chr_input; 
    int int_output_working = 0; 
    unsigned int uint_hex_working; 
    //while not a null byte 
    while(*chr_input_working != '\0') { 
    //if % 
    if (*chr_input_working == *"%") { 
     //then put correct char in 
     sscanf(chr_input_working + 1, "%02x", &uint_hex_working); 
     *chr_output_working = (char)uint_hex_working; 
     //printf("special char:%c, %c, %d<\n", *chr_output_working, (char)uint_hex_working, uint_hex_working); 
     //realloc 
     chr_input_working++; 
     chr_input_working++; 
     int_new_length -= 2; 
     chr_output = realloc(chr_output, int_new_length); 
     //output working must be the new pointer plys how many chars we've done 
     chr_output_working = chr_output + int_output_working; 
    } else { 
     //put char in 
     *chr_output_working = *chr_input_working; 
    } 
    //increment pointers and number of chars in output working 
    chr_input_working++; 
    chr_output_working++; 
    int_output_working++; 
    } 
    //last null byte 
    *chr_output_working = '\0'; 
    return chr_output; 
} 
+8

Podoba mi się część '*"% "'. : D –

+0

Dzięki, właśnie się dowiedziałem, że ''% ''działa. :) – Michael

+0

Jeśli zawiniesz swój kod między backticks ('), wtedy będą one sformatowane jako kod –

Odpowiedz

8

Jest doskonale OK, aby powrócić malloc „d bufory z funkcji w C, tak długo, jak udokumentować fakt, że robią. Wiele bibliotek to robi, mimo że nie działa żadna funkcja w standardowej bibliotece.

Jeśli możesz obliczyć (niezbyt pesymistyczną górną granicę) liczbę znaków, które trzeba tanio zapisać w buforze, możesz zaoferować funkcję, która to zrobi i pozwoli użytkownikowi zadzwonić.

Jest również możliwe, ale o wiele mniej wygodne, akceptowanie bufora do wypełnienia; Widziałem sporo bibliotek, które robią to w taki sposób:

/* 
* Decodes uri-encoded string encoded into buf of length len (including NUL). 
* Returns the number of characters written. If that number is less than len, 
* nothing is written and you should try again with a larger buffer. 
*/ 
size_t net_uri_to_text(char const *encoded, char *buf, size_t len) 
{ 
    size_t space_needed = 0; 

    while (decoding_needs_to_be_done()) { 
     // decode characters, but only write them to buf 
     // if it wouldn't overflow; 
     // increment space_needed regardless 
    } 
    return space_needed; 
} 

Teraz rozmówca jest odpowiedzialny za przydział, i zrobi coś

size_t len = SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH; 
char *result = xmalloc(len); 

len = net_uri_to_text(input, result, len); 
if (len > SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH) { 
    // try again 
    result = xrealloc(input, result, len); 
} 

(tutaj xmalloc i xrealloc są " bezpieczne "przydzielanie funkcji, które przygotowałem, aby pomijać sprawdzanie NULL.)

+1

+1 za wzmiankę o udokumentowaniu przydziału dynmmic! -) – alk

+2

Dwa miłe aspekty dotyczące oczekiwania na wywołanie bufora (+ rozmiar): 1. Dzwoniący może znać maksymalną długość z góry, aby mógł zdecydować się na użycie tablica przydzielona na stosie. 2.) Własność pamięci nie zostaje przeniesiona, co oznacza, że ​​zarówno alokacja, jak i dezalokacja odbywają się na stronie wywołującej - jest to krytyczne dla systemu Windows, jeśli osoba wywołująca znajduje się w innej bibliotece DLL niż strona wywołująca (nie można przydzielić pamięci w jednej bibliotece DLL). i zwolnij go w innym systemie Windows, ponieważ menedżer pamięci to moduł, a nie proces). –

+0

@FrerichRaabe: Nie wiedziałem o tym. Wiem, że pozwala on używać niestandardowych menedżerów pamięci zamiast 'malloc', co jest również przydatne w systemie Unix. –

2

Jest całkowicie OK, aby zwracać nowo-malloc -ed (i ewentualnie wewnętrznie realloc ed) wartości z funkcji, wystarczy udokumentować, że robią tak (jak to robisz tutaj).

innych oczywistych przedmiotów

  • Zamiast int int_length może chcesz użyć size_t. Jest to "typ bez znaku" (zwykle unsigned int lub unsigned long), który jest odpowiednim typem dla długości łańcuchów i argumentów dla malloc.
  • Początkowo trzeba przydzielić n + 1 bajtów, gdzie n jest długością łańcucha, ponieważ strlen nie zawiera kończącego 0 bajtu.
  • Należy sprawdzić pod kątem błędu malloc (zwracając NULL). Jeśli twoja funkcja przekaże niepowodzenie, udokumentuj to w komentarzu opisu funkcji.
  • sscanf jest dość ciężki do konwersji dwóch bajtów szesnastkowych. Nieprawidłowe , z wyjątkiem tego, że nie sprawdzasz, czy konwersja się udaje (co, jeśli dane wejściowe są źle sformułowane, możesz oczywiście zdecydować, że to jest problem dzwoniącego, ale ogólnie możesz chcieć sobie z tym poradzić). Możesz użyć isxdigit z <ctype.h>, aby sprawdzić cyfry szesnastkowe i/lub strtoul, aby wykonać konwersję.
  • Zamiast wykonywać jedną konwersję realloc dla każdej konwersji %, może być pożądane wykonanie końcowego "ponownego pobrania", jeśli jest to pożądane. Zauważ, że jeśli przydzielisz (powiedzmy) 50 bajtów dla ciągu znaków i okaże się, że wymaga to tylko 49, w tym końcowego bajtu 0, to może nie być warte zrobienia realloc.
2

Chodzi o to, że C jest wystarczająco niski, aby zmusić programistę do prawidłowego zarządzania pamięcią. W szczególności nie ma nic złego w zwracaniu ciągu znaków. Powszechnie używanym idiomem jest zwracanie zdeponowanych obiektów i wywoływanie ich w rozmowach telefonicznych free().

I tak, jeśli nie podoba ci się to podejście, zawsze możesz wziąć wskaźnik do łańcucha i zmodyfikować go z wnętrza funkcji (po ostatnim użyciu nadal będzie to free() d).

Jedną rzeczą, której nie uważam za konieczną, jest wyraźne zmniejszenie łańcucha. Jeśli nowy ciąg jest krótszy niż poprzedni, oczywiście jest w nim wystarczająco miejsca w kawałku pamięci starego łańcucha, więc nie musisz realloc().

(Pomijając fakt, że zapomniał przydzielić jeden dodatkowy bajt do kończącego znaku NUL, oczywiście ...)

I, jak zawsze, można po prostu wrócić innym za wskaźnikiem każdym razem, gdy funkcja jest zadzwonił, a nawet nie musisz w ogóle dzwonić pod numer realloc().

Jeśli akceptujesz ostatnią porcję dobrej porady: zaleca się, aby const - kwalifikować swoje ciągi wejściowe, aby dzwoniący mógł zagwarantować, że nie zmienisz ich. Korzystając z tego podejścia, można bezpiecznie wywołać funkcję na literałach ciągów, na przykład.

W sumie, chciałbym przepisać swoją funkcję tak:

char *unescape(const char *s) 
{ 
    size_t l = strlen(s); 
    char *p = malloc(l + 1), *r = p; 

    while (*s) { 
     if (*s == '%') { 
      char buf[3] = { s[1], s[2], 0 }; 
      *p++ = strtol(buf, NULL, 16); // yes, I prefer this over scanf() 
      s += 3; 
     } else { 
      *p++ = *s++; 
     } 
    } 

    *p = 0; 
    return r; 
} 

i nazywają to następująco:

int main() 
{ 
    const char *in = "testing123%5a%5b%5cabc"; 
    char *out = unescape(in); 
    printf("%s\n", out); 
    free(out); 

    return 0; 
} 
+0

Welp, nie wspomniałem wyraźnie o 'size_t' i' strtol() ', a także założyłem, że' malloc() 'nigdy nie zawodzi ... Uważaj! –

0

Chciałbym podejść do problemu w nieco inny sposób. Osobiście podzieliłbym twoją funkcję na dwie części.Pierwsza funkcja do obliczania rozmiaru, który musisz zaszeregować. Drugi zapisałby łańcuch wyjściowy do podanego wskaźnika (który został przydzielony poza funkcją). To oszczędza kilka połączeń do ponownego przydzielenia i pozostawia złożoność tak samo. Ewentualna funkcja znaleźć wielkość nowego łańcucha jest:

int getNewSize (char *string) { 
    char *i = string; 
    int size = 0, percent = 0; 
    for (i, size; *i != '\0'; i++, size++) { 
     if (*i == '%') 
      percent++; 
    } 
    return size - percent * 2; 
} 

Jednakże, jak wspomniano w innych odpowiedzi nie ma problemu w powrocie do malloc'ed bufor tak długo, jak dokumentować to!

+0

Zauważ, że jeśli zdecydujesz się na refaktor w ten sposób, możesz mieć swoją "funkcję obliczania przestrzeni" * również * sprawdź, czy adres URL jest dobrze uformowany (nie ma rzeczy takich jak% -! W środku).Czasami ludzie też wybierają coś w stylu hybrydowym: weryfikuj, opcjonalnie malloc, opcjonalnie przekształć w bufor (niezależnie od tego, czy użytkownik go dostarczył, czy malloc-ed), zwróć kilka informacji (za pośrednictwem wskaźników 'struct' lub dostarczonych przez dzwoniącego), itp. – torek

0

Dodatkowo, co zostało już wspomniane w innych księgowaniach, należy również udokumentować fakt, że ciąg jest ponownie przydzielany. Jeśli Twój kod jest wywoływany za pomocą stałego ciągu lub ciągu przydzielonego z alloca, nie możesz go ponownie rozdzielić.

0

Myślę, że masz rację, martwiąc się o podział mallocsów i uwolnień. Co do zasady, cokolwiek czyni to, jest jego właścicielem i powinno go uwolnić.

W tym przypadku, gdy łańcuchy są względnie małe, jedną z dobrych procedur jest uczynienie bufora ciągu większego niż jakikolwiek możliwy ciąg, który może zawierać. Na przykład adresy URL mają de facto limit około 2000 znaków, więc jeśli zdeformujesz 10000 znaków, możesz zapisać dowolny możliwy adres URL.

Inną sztuczką jest przechowywanie zarówno długości, jak i pojemności sznurka z przodu, tak aby (int)*mystring == length of string i (int)*(mystring + 4) == capacity ciągu. W związku z tym sam ciąg zaczyna się tylko na 8 pozycji *(mystring+8). W ten sposób można przekazać pojedynczy wskaźnik do łańcucha i zawsze wiedzieć, jak długo jest i ile pamięci ma ciąg. Możesz tworzyć makra, które automatycznie generują te przesunięcia i tworzą "ładny kod".

Wartość korzystania z buforów w ten sposób polega na tym, że nie trzeba dokonywać realokacji. Nowa wartość zastępuje starą wartość i aktualizujesz długość na początku łańcucha.

+1

Myślę, że "kodowanie rozmiaru ciągu w technice pierwszych kilobajtów" jest bardzo nieprzyjemne, ponieważ jest raczej rzadkie (nigdy nie widziałem tego w praktyce), a kompilator nie może pomóc, jeśli zapomnisz o tym fakcie. Więc jeśli masz ciąg "Hello", drukowanie może po prostu wydrukować pojedynczy znak, ponieważ większość bajtów początkowego int wynosi zero. Niezła rzecz do debugowania. : -/ –

+0

Podczas korzystania z treści ciągu można go nazwać "* (mystring + 8)". Na przykład: printf ("% s \ n", * (mystring + 8)); można użyć makra, jeśli jest to potrzebne. Alternatywą jest zdefiniowanie struktury, ale wtedy musisz radzić sobie z zagnieżdżonymi wskaźnikami. Z mojego doświadczenia wynika, że ​​łatwiej jest używać opisanej metody niż używać zagnieżdżonych wskaźników podczas manipulowania krótkimi łańcuchami. –

Powiązane problemy