Jakie są wady, które doprowadzają Cię do szaleństwa w interfejsach API C (w tym bibliotek standardowych, bibliotek stron trzecich i nagłówków w projekcie)? Celem jest zidentyfikowanie pułapek projektowania API w C, aby osoby piszące nowe biblioteki C mogły uczyć się na błędach z przeszłości.
Wyjaśnij, dlaczego wada jest zła (najlepiej z przykładem) i spróbuj zasugerować poprawę. Chociaż Twoje rozwiązanie może nie być praktyczne w rzeczywistości (jest za późno, aby je naprawić strncpy
), powinno dać przewagę przyszłym autorom bibliotek.
Chociaż celem tego pytania są interfejsy API C, mile widziane są problemy, które wpływają na możliwość korzystania z nich w innych językach.
Proszę podać jedną wadę na odpowiedź, aby demokracja mogła uporządkować odpowiedzi.
źródło
malloc
łańcuch może to naprawić. Myślę, że dawanie dobrego przykładu z pierwszą odpowiedzią może naprawdę pomóc w rozwoju tego pytania. Dzięki!Odpowiedzi:
Funkcje z niespójnymi lub nielogicznymi wartościami zwracanymi. Dwa dobre przykłady:
1) Niektóre funkcje systemu Windows, które zwracają UCHWYT, używają NULL / 0 dla błędu (CreateThread), niektóre używają INVALID_HANDLE_VALUE / -1 dla błędu (CreateFile).
2) Funkcja POSIX „czas” zwraca błąd (time_t) -1 ”po błędzie, co jest naprawdę nielogiczne, ponieważ„ time_t ”może być albo znakiem, albo niepodpisanym.
źródło
int time(time_t *out);
iBOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
.Funkcje lub parametry o nieopisowych lub afirmatywnie mylących nazwach. Na przykład:
1) CreateFile w interfejsie API systemu Windows tak naprawdę nie tworzy pliku, lecz tworzy uchwyt pliku. Może utworzyć plik, podobnie jak „open”, jeśli zostanie poproszony o podanie parametru. Ten parametr ma wartości o nazwie „CREATE_ALWAYS” i „CREATE_NEW”, których nazwy nawet nie wskazują na ich semantykę. (Czy „CREATE_ALWAYS” oznacza, że nie powiedzie się, jeśli plik istnieje? Czy też tworzy na nim nowy plik? Czy „CREATE_NEW” oznacza, że zawsze tworzy nowy plik i kończy się niepowodzeniem, jeśli plik już istnieje? Czy też tworzy nowy plik na górze?)
2) pthread_cond_wait w interfejsie API pthreads POSIX, który pomimo swojej nazwy jest bezwarunkowym oczekiwaniem.
źródło
pthread_cond_wait
nie znaczy „warunkowo czekać”. Odnosi się do faktu, że czekasz na zmienną warunkową .Nieprzezroczyste typy przekazywane przez interfejs jako uchwyty usuwane typu. Problem polega oczywiście na tym, że kompilator nie może sprawdzić kodu użytkownika pod kątem poprawności typów argumentów.
Występuje w różnych formach i smakach, w tym między innymi:
void*
nadużycieużywanie
int
jako uchwytu zasobu (przykład: biblioteka CDI)argumenty ciągłe
Im bardziej wyraźne typy (= nie można użyć w pełni zamiennie) są odwzorowane na ten sam typ usunięty, tym gorzej. Oczywiście rozwiązaniem jest po prostu zapewnienie nieprzejrzystych wskaźników typu „bezpieczny” na typach (przykład C):
źródło
Funkcje z niekonsekwentnymi i często kłopotliwymi konwencjami zwracania łańcucha.
Na przykład getcwd pyta o bufor dostarczony przez użytkownika i jego rozmiar. Oznacza to, że aplikacja musi ustawić dowolny limit długości bieżącego katalogu lub wykonać coś takiego ( z CCAN ):
Moje rozwiązanie: zwróć
malloc
ciąg ed. Jest prosty, solidny i nie mniej wydajny. Z wyjątkiem platform wbudowanych i starszych systemów, wmalloc
rzeczywistości jest dość szybki.źródło
snprintf(buf, 32, "%d", n)
przypadku, gdy długość wyjściowa jest przewidywalna (na pewno nie więcej niż 30, chyba że w twoim systemieint
jest naprawdę duża). Rzeczywiście, malloc nie jest dostępny na wielu systemach, ale w środowiskach biurkowych i serwerowych jest, i działa naprawdę dobrze.Funkcje przyjmujące / zwracające złożone typy danych według wartości lub wykorzystujące wywołania zwrotne.
Jeszcze gorzej, jeśli wspomniany typ jest unią lub zawiera pola bitowe.
Z perspektywy osoby dzwoniącej w języku C są one w rzeczywistości OK, ale nie piszę w języku C lub C ++, chyba że jest to wymagane, dlatego zwykle dzwonię przez FFI. Większość FFI nie obsługuje związków ani pól bitowych, a niektóre (takie jak Haskell i MLton) nie obsługują struktur przekazywanych przez wartość. Dla tych, którzy potrafią obsługiwać struktury według wartości, przynajmniej Common Lisp i LuaJIT są zmuszane do powolnych ścieżek - Interfejs Common Foreign Function interfejsu Lisp musi wykonywać wolne wywołanie za pośrednictwem libffi, a LuaJIT odmawia skompilowania przez JIT ścieżki kodu zawierającej wywołanie. Funkcje, które mogą wywoływać z powrotem do hostów, wyzwalają również wolne ścieżki w LuaJIT, Java i Haskell, przy czym LuaJIT nie jest w stanie skompilować takiego wywołania.
źródło