Pytanie Czy rzucam wynik malloc?


W to pytanie, ktoś zaproponował w komentarz że powinienem nie rzucić wynik malloc, tj.

int *sieve = malloc(sizeof(int) * length);

zamiast:

int *sieve = (int *) malloc(sizeof(int) * length);

Dlaczego tak się stało?


2043
2018-03-03 10:13


pochodzenie


Ponadto łatwiej jest pisać sito = malloc (sizeof * sito * length); - William Pursell
stackoverflow.com/q/7545365/168175 - Flexo♦
@KarolyHorvath nie jest to jedyny powód. Drugą (i najważniejszą, IMHO) jest uczynienie kodu bardziej elastycznym (ale nie mniej bezpiecznym lub silniejszym), jeśli rodzaj sieve zmienia się z int na, powiedzmy, float. malloc( sizeof *sieve * length ); będzie działać niezależnie od typu, podczas gdy obsada wymaga konserwacji, niczego nie dodaje i może utrudnić śledzenie błędów. - MestreLion
@MestreLion: "i może tworzyć trudne do śledzenia błędy" - wszystko, co musisz zrobić, to włączyć kompilowanie ostrzeżeń i nigdy stanie się. OTOH, jeśli nie ... naprawdę na to zasługujesz. i, znowu, nie jestem zwolennikiem korzystania z tego .. wszystko, co mówię, to naprawdę nie jest tak ważne ... to jest w zasadzie niegroźny :) - Karoly Horvath
Zobacz też W szczególności, co jest niebezpieczne w rzucaniu wyniku malloc()? - Jonathan Leffler


Odpowiedzi:


Nie; ty nie rób tego rzucić wynik, ponieważ:

  • Jest niepotrzebne, ponieważ void * jest automatycznie i bezpiecznie promowany do dowolnego innego typu wskaźnika w tym przypadku.
  • Dodaje bałaganu do kodu, rzuty nie są bardzo łatwe do odczytania (szczególnie jeśli typ wskaźnika jest długi).
  • Powtarza się, co jest ogólnie złe.
  • Może ukryć błąd, jeśli zapomniałeś o tym <stdlib.h>. Może to spowodować awarie (lub, co gorsza, nie spowodować awarię do czasu później w zupełnie innej części kodu). Zastanów się, co się stanie, jeśli wskaźniki i liczby całkowite są różnej wielkości; następnie ukrywasz ostrzeżenie przez odlewanie i możesz stracić fragmenty swojego zwróconego adresu. Uwaga: od C11 funkcje niejawne zostały usunięte z C, a ten punkt nie jest już istotny, ponieważ nie ma automatycznego założenia, że ​​niezadeklarowane funkcje zwracają int.

Jako wyjaśnienie zauważ, że powiedziałem "nie rzucasz", a nie "ty nie potrzeba do obsady. "Moim zdaniem, nie uwzględniam obsady, nawet jeśli masz rację. Nie ma po prostu żadnych korzyści z tego powodu, ale kilka potencjalnych zagrożeń, w tym obsada, oznacza, że ​​nie wiesz o ryzyku.

Zwróć też uwagę, jak podkreślają komentatorzy, że powyższe mówi o prostym C, a nie C ++. Bardzo mocno wierzę w C i C ++ jako osobne języki.

Aby dodać więcej, twój kod niepotrzebnie powtarza informacje o typie (int), co może powodować błędy. Lepiej jest usunąć zaznaczenie wskaźnika używanego do przechowywania zwracanej wartości, aby "zablokować" te dwa elementy razem:

int *sieve = malloc(length * sizeof *sieve);

To także przesuwa length z przodu dla lepszej widoczności i upuszcza nadmiarowe nawiasy za pomocą sizeof; one są potrzebne tylko gdy argument jest nazwą typu. Wiele osób zdaje się nie znać (lub ignorować) tego, co sprawia, że ​​ich kod jest bardziej szczegółowy. Zapamiętaj: sizeof nie jest funkcją! :)


Podczas przenoszenia length do przodu może zwiększyć widoczność w niektórych rzadkich przypadkach, należy również zwrócić uwagę, że w ogólnym przypadku powinno być lepiej napisać wyrażenie:

int *sieve = malloc(sizeof *sieve * length);

Od zachowania sizeof po pierwsze, w tym przypadku zapewnia co najmniej mnożenie size_t matematyka.

Porównać: malloc(sizeof *sieve * length * width) vs. malloc(length * width * sizeof *sieve) drugi może przepełnić length * width gdy width i length są mniejsze niż size_t.


1917
2018-03-03 10:17



@unwind Nie, nawet w ścisłym kodzie C używam obsady do przenoszenia. Co rozumiem przez przenośność jest przenośny dla starszych wersji standardu c, gdzie void * nie jest promowany. Cytuję z K & R "The C Programming Language": "In C, właściwą metodą jest stwierdzenie, że malloc zwraca wskaźnik do unieważnienia, a następnie wyraźnie przymusić wskaźnik do pożądanego typu za pomocą odlewu". Powodem, dla którego narzucam taki wymóg jest to, że nie zawsze masz wybór jakiego kompilatora możesz użyć. - chacham15
@ chacham15 Chociaż były wcześniejsze wersje C język gdzie obsada była konieczna (K & R C nie posiadał void* type!), te wersje nigdy nie były standard. Wymagało tego C89 (pierwsza standardowa wersja) malloc powrót void* i to void* być domyślnie wymienialnym. - jamesdlin
@Soumen To może być zbyt mało znane; bez uwzględnienia typu zwrotu int zostanie przyjęta, która może być mniejsza niż void *, więc zadanie może wygenerować ostrzeżenie, które zostanie zniesione przez obsadę. Widzieć to pytanie na przykład. - unwind
@unwind Większość kompilatorów ostrzega o domyślnie zdefiniowanej funkcji ... (więc nie widzę niebezpieczeństwa obsady) - DarthRubik
@ n.m. Ok. Myślę, że źle jest założyć, że każdy czytający tutaj ma określony kompilator. Poza tym, odkąd C11 nie ma całej koncepcji "funkcji niejawnej", nie wiedziałem o tym. Nadal nie widzę sensu dodawania bezsensownej obsady. Czy ty też to robisz int x = (int) 12; tylko po to, żeby wszystko było jasne? - unwind


W języku C nie trzeba rzutować wartości zwracanej przez malloc. Wskaźnik do anulowania zwracany przez malloc jest automagicznie przekonwertowany na właściwy typ. Jeśli jednak chcesz, aby Twój kod kompilował się z kompilatorem C ++, wymagana jest obsada. Preferowaną alternatywą wśród społeczności jest użycie następujących elementów:

int *sieve = malloc(sizeof *sieve * length);

co dodatkowo uwalnia od konieczności martwienia się o zmianę prawej strony wyrażenia, jeśli kiedykolwiek zmienisz typ sieve.

Rzędy są złe, jak to zauważyli ludzie. Specjalnie rzutuje rzut.


327
2018-03-03 10:17



@MAKZ Twierdzę, że tak malloc(length * sizeof *sieve) sprawia, że ​​wygląda sizeof jest zmienną - tak myślę malloc(length * sizeof(*sieve)) jest bardziej czytelny. - Michael Anderson
I malloc(length * (sizeof *sieve)) jeszcze bardziej czytelny. MOIM ZDANIEM. - Toby Speight
@ Michael Anderson () odsuń na bok, zauważ, że twój sugerowany styl zmienił kolejność. Zastanów się, kiedy licznik elementów jest obliczany jak length*width, utrzymując sizeof najpierw w tym przypadku zapewnia się rozmnażanie co najmniej size_t matematyka. Porównać malloc(sizeof( *ptr) * length * width) vs. malloc(length * width * sizeof (*ptr)) - 2 może przepełnić length*width gdy width,length są mniejszymi typami size_t. - chux
@chux to nie jest oczywiste, ale odpowiedź została zmieniona, więc mój komentarz jest mniej trafny - pierwotna sugestia była malloc(sizeof *sieve * length) - Michael Anderson
C nie jest C ++. Udawanie, że są, ostatecznie doprowadzi do zamieszania i smutku. Jeśli używasz C ++, to rzutowanie w stylu C jest również złe (chyba że używasz bardzo starego kompilatora C ++). I static_cast>() (lub reinterpret_cast<>() ) nie jest kompatybilny z żadnym dialektem C. - David C.


ty zrobić obsada, ponieważ:

  • Tworzy twój kod bardziej przenośny między C i C ++, a jak pokazuje doświadczenie SO, wielu programistów twierdzi, że piszą w C, kiedy naprawdę piszą w C ++ (lub C plus lokalne rozszerzenia kompilatora).
  • W przeciwnym razie może ukryć błąd: zanotuj wszystkie przykłady SO dezorientujące kiedy pisać type * przeciw type **.
  • Pomysł, że powstrzymuje cię od zauważenia cię, nie udało się #include Odpowiedni plik nagłówkowy pomija las dla drzew. To samo, co powiedzenie "nie martw się tym, że nie poprosiłeś kompilatora, by narzekał na brak oglądania prototypów - ta brzydka stdlib.h jest PRAWDZIWĄ ważną rzeczą do zapamiętania!"
  • Wymusza to dodatkowa kontrola poznawcza. Umieszcza (rzekomy) pożądany typ tuż obok arytmetycznej, którą robisz dla surowego rozmiaru tej zmiennej. Założę się, że mógłbyś zrobić badanie SO, które to pokazuje malloc() błędy są łapane znacznie szybciej, gdy jest rzutowanie. Podobnie jak w przypadku twierdzeń, adnotacje, które ujawniają zamiary, zmniejszają liczbę błędów.
  • Powtarzanie się w sposób, który maszyna może sprawdzić, to często wspaniały pomysł. W rzeczywistości takie właśnie jest twierdzenie, a to użycie rzutu jest stwierdzeniem. Asercje są wciąż najbardziej ogólną techniką poprawnego kodu, ponieważ Turing wpadł na ten pomysł wiele lat temu.

292
2018-02-14 16:15



@ulidtko Na wypadek, gdybyś nie wiedział, możliwe jest napisanie kodu, który kompiluje zarówno C jak i C ++. W rzeczywistości większość plików nagłówkowych jest taka, i często zawierają kod (makra i funkcje wbudowane). Mający .c/.cpp plik do kompilacji, ponieważ oba nie są przydatne bardzo często, ale jednym z przypadków jest dodanie C ++ throw wsparcie podczas kompilacji z kompilatorem C ++ (ale return -1; po kompilacji z kompilatorem C lub cokolwiek innego). - hyde
Gdyby ktoś miał wywołania malloc inline w nagłówku, nie byłbym pod wrażeniem, #ifdef __cplusplus i extern "C" {} są dla tego zadania, nie dodając dodatkowych rzutów. - paulm
Cóż, punkt 1 jest nieistotny, ponieważ C! = C ++, inne punkty są również trywialne, jeśli używasz zmienna w Twoim malloc połączenie: char **foo = malloc(3*sizeof(*foo)); jeśli jest całkowicie pełny dowód: 3 wskaźniki do oznaczeń char. następnie wykonaj pętlę i wykonaj foo[i] = calloc(101, sizeof(*(foo[i])));. Przydziel tablicę 101 znaków, starannie zainicjowanych na zera. Nie trzeba rzucać. zmień deklarację na unsigned char lub jakikolwiek inny typ, jeśli o to chodzi, i nadal jesteś dobry - Elias Van Ootegem
Kiedy myślałem, że to mam, nadchodzi! Fantastyczna odpowiedź. To pierwszy raz w StackOverflow, że daje +1 dwie przeciwne odpowiedzi! +1 Nie, nie rzucasz, a +1 Tak, rzucasz! LOL. Jesteście wspaniali. A ja i moi studenci, myślałem: rzucam. Rodzaj błędów popełnianych przez uczniów jest łatwiej zauważany podczas rzucania. - Dr Beco
@Leushenko: Powtarzanie się w sposób, który nie może być zatwierdzony przez maszynę ani przez lokalną inspekcję, jest złe. Powtarzanie się w sposób, który można zwalidować za pomocą takich środków, jest mniej szkodliwe. Dany struct Zebra *p; ... p=malloc(sizeof struct Zebra);malloc nie może uniknąć powielania informacji o typie p, ale ani kompilator, ani kontrola kodu lokalnego nie wykryłaby żadnego problemu, gdyby jeden typ się zmienił, ale drugi nie. Zmień kod na p=(struct Zebra*)malloc(sizeof struct Zebra); i kompilator skrzyczy, jeśli typ rzutowania nie pasuje p, i lokalny inspekcja ujawni ... - supercat


Jak podano inne, nie jest potrzebne dla C, ale dla C ++. Jeśli myślisz, że skompilujesz swój kod C za pomocą kompilatora C ++, z jakich powodów możesz zamiast tego użyć makra, na przykład:

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

W ten sposób możesz napisać to w bardzo kompaktowy sposób:

int *sieve = NEW(int, 1);

i będzie kompilował dla C i C ++.


148
2018-03-03 11:17



Ponieważ mimo to używasz makra, dlaczego nie używasz new w definicji C ++? - Hosam Aly
Ponieważ nie ma powodu, aby to robić. Dotyczy to głównie programów C skompilowanych z kompilatorem C ++. Jeśli zamierzasz używać "nowego", jedyne, co masz, to problemy. Potrzebujesz także makra za darmo. I potrzebujesz makra, aby zwolnić tablicę, różnicowanie, które nie istnieje w C. - quinmars
Nie wspominając, że to nie ty zwalniasz pamięć, ale może bibliotekę C, której używasz, itd. Wiele możliwych problemów bez żadnego zysku. - quinmars
@Hosam: Tak, zdecydowanie. Jeśli użyjesz new musisz użyć delete i jeśli używasz malloc() musisz ciebie free(). Nigdy nie mieszaj ich. - Graeme Perrow
Jeśli ktoś przyjmie takie podejście, wywoła makro NEW jest prawdopodobnie złym pomysłem, ponieważ zasób nigdy nie jest zwracany za pomocą delete (lub DELETE), więc mieszacie swoje słownictwo. Zamiast tego, nazwij go MALLOC, albo raczej CALLOC w tym przypadku miałoby więcej sensu. - mah


Od Wikipedia

Zalety odlewania

  • Włączenie obsady może pozwolić na kompilację programu lub funkcji C jako C ++.

  • Obsada pozwala na wersje malloc sprzed 1989 r., Które pierwotnie zwróciły znak *.

  • Przesyłanie może pomóc programistom w wykryciu niespójności w rozmiarach typów w przypadku zmiany typu wskaźnika docelowego, szczególnie jeśli wskaźnik jest zadeklarowany z dala od wywołania funkcji malloc () (chociaż nowoczesne kompilatory i statyczne analizatory mogą ostrzegać o takim zachowaniu bez wymagania rzutowania).

Wady odlewania

  • Zgodnie z normą ANSI C obsada jest zbędna.

  • Dodanie rzutowania może maskować niepowodzenie w dołączeniu nagłówka stdlib.h, w   który znajduje się prototyp dla malloc. W przypadku braku   prototyp dla malloc, standard wymaga kompilatora C.   Załóżmy, że malloc zwraca int. Jeśli nie ma obsady, pojawia się ostrzeżenie   wydane, gdy ta liczba całkowita jest przypisana do wskaźnika; jednak z   obsada, to ostrzeżenie nie jest produkowane, ukrywając błąd. Na pewno   architektury i modele danych (takie jak LP64 w systemach 64-bitowych, gdzie   long i wskaźniki są 64-bitowe, a int - 32-bitowe), ten błąd może   faktycznie powodują niezdefiniowane zachowanie, jak to domyślnie deklaruje   malloc zwraca wartość 32-bitową, podczas gdy faktycznie zdefiniowana funkcja   zwraca 64-bitową wartość. W zależności od konwencji wywoływania i pamięci   układ, może to spowodować rozbicie stosu. Ten problem jest mniej prawdopodobny   pozostać niezauważonym w nowoczesnych kompilatorach, ponieważ jednolicie produkują   ostrzeżenia, że ​​została użyta niezadeklarowana funkcja, więc pojawi się ostrzeżenie   wciąż się pojawiają. Na przykład domyślnym zachowaniem GCC jest wyświetlanie znaku a   ostrzeżenie, które brzmi "niekompatybilna dorozumiana deklaracja wbudowanego   funkcja "niezależnie od tego, czy obsada jest obecna, czy nie.

  • Jeśli typ wskaźnika zostanie zmieniony w deklaracji, można   również, trzeba zmienić wszystkie linie, w których nazywa się malloc i rzutuje.

Mimo że malloc bez odlewania jest metodą preferowaną i wybiera ją większość doświadczonych programistówpowinieneś używać tego, co lubisz, mając świadomość problemów.

tzn. Jeśli chcesz skompilować program C jako C ++ (chociaż są to osobne języki), powinieneś użyć malloc z odlewem.


96
2017-10-09 21:23



Co robi "Przesyłanie może pomóc programistom w wykryciu niespójności w zakresie rozmiaru czcionki w przypadku zmiany typu wskaźnika docelowego, szczególnie jeśli wskaźnik jest zadeklarowany z dala od malloc() połączenie"Czy możesz podać przykład? - Cool Guy
@Spoko gość: Zobacz wcześniejszy komentarz do innej odpowiedzi. Ale zauważ, że p = malloc(sizeof(*p) * count) idiom automatycznie pobiera zmiany w typie, więc nie musisz otrzymywać ostrzeżeń i zmieniać niczego. To nie jest prawdziwa przewaga, a najlepsza alternatywa dla odrzucania. - Peter Cordes
To jest właściwa odpowiedź: są plusy i minusy, i sprowadza się to do kwestii smaku (chyba że kod musi się kompilować jako C ++ - wtedy obsada jest obowiązkowa). - Peter A. Schneider
Punkt 3 jest dyskusyjny, ponieważ Jeśli typ wskaźnika zostanie zmieniony w deklaracji, należy sprawdzić każde wystąpienie malloc, realloc i wolny inolving tego typu. Casting zmusi cię do zrobienia właśnie tego. - Michaël Roy


W języku C można niejawnie przekształcić wskaźnik pustych wierszy w dowolny inny wskaźnik, więc rzutowanie nie jest konieczne. Korzystanie z nich może zasugerować przypadkowemu obserwatorowi, że istnieje jakiś powód, dla którego ktoś jest potrzebny, co może wprowadzać w błąd.


94
2018-03-03 10:18





Nie rzucasz wyniku malloc, ponieważ czyni to bezużytecznym bałaganem dla twojego kodu.

Najczęstszym powodem, dla którego ludzie rzucają rezultat malloc, jest to, że nie są pewni, jak działa język C. To jest znak ostrzegawczy: jeśli nie wiesz, jak działa dany mechanizm językowy nie rób tego zgadnij. Sprawdź go lub zapytaj na przepełnieniu stosu.

Niektóre komentarze:

  • Wskaźnik pusty może zostać przekonwertowany na / z dowolnego innego wskaźnika bez wyraźnego rzutu (C11 6.3.2.3 i 6.5.16.1).

  • C ++ nie zezwoli jednak na niejawne rzutowanie między void* i inny typ wskaźnika. Tak więc w C ++ obsada byłaby poprawna. Ale jeśli programujesz w C ++, powinieneś użyć new a nie malloc (). I nigdy nie powinieneś kompilować kodu C używając kompilatora C ++.

    Jeśli potrzebujesz obsługi zarówno C jak i C ++ z tym samym kodem źródłowym, użyj przełączników kompilatora, aby oznaczyć różnice. Nie próbuj zaspokajać obu standardów językowych za pomocą tego samego kodu, ponieważ nie są one zgodne.

  • Jeśli kompilator języka C nie może znaleźć funkcji, ponieważ zapomniałeś dołączyć nagłówka, otrzymasz komunikat o błędzie kompilatora / linkera. Więc jeśli zapomniałeś dołączyć <stdlib.h> to nie jest biggie, nie będziesz w stanie zbudować swojego programu.

  • Na starożytnych kompilatorach, które są zgodne z wersją standardu, która ma więcej niż 25 lat, zapominając o tym <stdlib.h> spowodowałoby niebezpieczne zachowanie. Ponieważ w tym starodawnym standardzie funkcje bez widocznego prototypu niejawnie przekształciły typ zwracany na int. Rzucenie wyniku z malloc wyraźnie ukryłoby ten błąd.

    Ale to naprawdę nie jest problem. Nie korzystasz z komputera w wieku 25 lat, więc dlaczego miałbyś używać 25-letniego kompilatora?


83
2018-03-20 15:53



"bezsensowny bałagan" to lekceważąca hiperbola, która ma tendencję do wykolejenia możliwości przekonywania kogokolwiek, kto jeszcze się z tobą nie zgadza. Obsada z pewnością nie jest bezcelowa; Odpowiedzi Rona Burka i Kaza przemawiają na korzyść castingu, z którym bardzo się zgadzam. Czy te obawy ważą więcej niż obawy, o których wspomniałeś, to rozsądne pytanie. Dla mnie twoje obawy wyglądają na stosunkowo niewielkie w porównaniu do ich. - Don Hatch
"Nieważny wskaźnik można przekonwertować na / z dowolnego innego typu wskaźnika bez jawnego rzutowania" nie jest obsługiwany przez 6.3.2.3. Być może myślisz o "wskaźniku do dowolnego typu obiektu"? "void point" i "wskaźnik do funkcji" nie są tak łatwo wymienialne. - chux
Rzeczywiście odniesienie było niekompletne. Odpowiednią częścią "implikatury" jest zasada prostego przypisania 6.5.16.1. "jeden operand jest wskaźnikiem do typu obiektu, a drugi jest wskaźnikiem do kwalifikowanej lub niewykwalifikowanej wersji pustki". Dodałem to odniesienie do odpowiedzi pod kątem kompletności. - Lundin


W C otrzymujesz niejawną konwersję void* do dowolnego innego wskaźnika (danych).


82
2018-03-03 10:16



@Jens: OK, może bardziej właściwe sformułowanie to "niejawna konwersja". Podobnie jak użycie zmiennej całkowej w wyrażeniu zmiennoprzecinkowym. - EFraim
@EFraim To faktycznie doprowadziłoby do obsady i niejawnego. - Mad Physicist


Przesyłam wartość zwróconą przez malloc() nie jest teraz konieczne, ale chciałbym dodać jeden punkt, który wydaje się nikt nie wskazał:

W dawnych czasach, to jest wcześniej ANSI C zapewnia void * jako ogólny rodzaj wskaźników, char * jest typem takiego użycia. W takim przypadku obsada może wyłączyć ostrzeżenia kompilatora.

Odniesienie: C FAQ


62
2018-06-09 17:31



Zamykanie ostrzeżeń kompilatora jest złym pomysłem. - Albert van der Horst
@AlbertvanderHorst Nie, jeśli robisz to, rozwiązując dokładny problem, ostrzega cię o ostrzeżeniu. - Dan Bechard
@Dan. Jeśli rozwiązując dokładnie problem oznacza przepisanie podprocedury, aby zwrócić nowoczesne typy ANSI C zamiast char *, zgadzam się. Nie nazwałbym tego zamykaniem kompilatora. Nie poddawaj się menedżerom, którzy twierdzą, że nie ma ostrzeżeń kompilatora, zamiast używać ich przy każdej rekompilacji, aby znaleźć możliwe problemy. Groetjes Albert - Albert van der Horst


Nie jest obowiązkowe przekazywanie wyników malloc, ponieważ wraca void* i void*można wskazać na dowolny typ danych.


48
2018-02-07 22:22