Czy mogę rzucić wynik malloc?
W tej kwestii , ktoś sugerowane w komentarzu , że powinienem nie rzucać wynik malloc
, czyli
int *sieve = malloc(sizeof(int) * length);
zamiast:
int *sieve = (int *) malloc(sizeof(int) * length);
Dlaczego tak się dzieje?
Odpowiedzi
Nie ; Państwo nie rzucać wynik, ponieważ:
- Nie jest to konieczne, ponieważ
void *
w tym przypadku jest automatycznie i bezpiecznie promowane do dowolnego innego typu wskaźnika. - Dodaje bałagan do kodu, rzuty nie są łatwe do odczytania (szczególnie jeśli typ wskaźnika jest długi).
- To sprawia, że powtarzasz się, co jest ogólnie złe.
- Może ukryć błąd, jeśli zapomnisz dołączyć
<stdlib.h>
. Może to być przyczyną awarii (lub, co gorsza, nie spowodować katastrofę aż później w jakiś sposób zupełnie innej części kodu). Zastanów się, co się stanie, jeśli wskaźniki i liczby całkowite mają różne rozmiary; wtedy ukrywasz ostrzeżenie, przesyłając i możesz stracić fragmenty swojego zwróconego adresu. Uwaga: od C99 niejawne funkcje zniknęły z C i ten punkt nie ma już znaczenia, ponieważ nie ma automatycznego założenia, że niezadeklarowane funkcje zwracająint
.
Dla wyjaśnienia zwróć uwagę, że powiedziałem „nie rzucasz”, a nie „nie musisz przesyłać”. Moim zdaniem to nieudana obsada, nawet jeśli dobrze to zrobili. Po prostu nie ma z tego żadnych korzyści, ale wiele potencjalnych zagrożeń, w tym obsada, wskazuje, że nie wiesz o ryzyku.
Zauważ również, jak zauważają komentatorzy, że powyższe mówi o prostym C, a nie C ++. Bardzo mocno wierzę w C i C ++ jako oddzielne języki.
Aby dodać coś więcej, kod niepotrzebnie powtarza informacje o typie ( int
), co może powodować błędy. Lepiej jest usunąć odniesienie do wskaźnika używanego do przechowywania wartości zwracanej, aby "zablokować" oba razem:
int *sieve = malloc(length * sizeof *sieve);
Spowoduje to również przesunięcie length
do przodu w celu zwiększenia widoczności i upuszczenie zbędnych nawiasów za pomocą sizeof
; one są potrzebne tylko wtedy, gdy argument jest nazwą typu. Wydaje się, że wiele osób tego nie wie (lub ignoruje), co sprawia, że ich kod jest bardziej szczegółowy. Pamiętaj: sizeof
nie jest funkcją! :)
Przesuwając length
się do przodu może w niektórych rzadkich przypadkach zwiększyć widoczność, należy też zwrócić uwagę, że w ogólnym przypadku lepiej napisać wyrażenie jako:
int *sieve = malloc(sizeof *sieve * length);
Ponieważ zachowanie sizeof
pierwszego, w tym przypadku, zapewnia mnożenie przynajmniej przy użyciu size_t
matematyki.
Porównaj: w malloc(sizeof *sieve * length * width)
porównaniu malloc(length * width * sizeof *sieve)
z drugim może przepełnić length * width
kiedy width
i length
są mniejsze typy niż size_t
.
W C nie musisz rzutować wartości zwracanej z malloc
. Wskaźnik do void zwracany przez malloc
jest automagicznie konwertowany na właściwy typ. Jeśli jednak chcesz, aby Twój kod był kompilowany za pomocą kompilatora C ++, wymagane jest rzutowanie. Preferowaną alternatywą wśród społeczności jest użycie:
int *sieve = malloc(sizeof *sieve * length);
co dodatkowo uwalnia Cię od martwienia się o zmianę prawej strony wyrażenia, jeśli kiedykolwiek zmienisz jego typ sieve
.
Gipsy są złe, jak ludzie zauważyli. Szczególnie rzuca wskaźnik.
Państwo nie rzucać, ponieważ:
- To sprawia, że twój kod jest bardziej przenośny między C i C ++, a jak pokazuje doświadczenie SO, wielu programistów twierdzi, że pisze w C, podczas gdy naprawdę piszą w C ++ (lub C plus lokalne rozszerzenia kompilatora).
- Niezastosowanie się do tego może ukryć błąd : zwróć uwagę na wszystkie przykłady tak mylących, kiedy pisać,
type *
a kiedytype **
. - Pomysł, że dzięki temu nie zauważysz, że nie udało się
#include
uzyskać odpowiedniego pliku nagłówkowego, pomija las dla drzew . To to samo, co powiedzenie „nie martw się, że nie poprosiłeś kompilatora o narzekanie, że nie widzi prototypów - ten nieznośny stdlib.h jest PRAWDZIWĄ ważną rzeczą do zapamiętania! - Wymusza dodatkową kontrolę poznawczą . Umieszcza (rzekomo) pożądany typ tuż obok arytmetyki, którą wykonujesz dla surowego rozmiaru tej zmiennej. Założę się, że możesz przeprowadzić badanie SO, które pokazuje, że
malloc()
owady są łapane znacznie szybciej, gdy jest obsada. Podobnie jak w przypadku asercji, adnotacje ujawniające zamiar zmniejszają liczbę błędów. - Powtarzanie się w sposób, który maszyna może sprawdzić, jest często świetnym pomysłem. W rzeczywistości tym jest asercja, a użycie rzutowania jest asercją. Asercje są nadal najbardziej ogólną techniką, jaką dysponujemy, aby uzyskać poprawny kod, ponieważ Turing wpadł na ten pomysł wiele lat temu.
Jak powiedzieli inni, nie jest potrzebny w C, ale niezbędny w C ++. Jeśli myślisz, że zamierzasz skompilować swój kod C za pomocą kompilatora C ++, z dowolnego powodu 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 nadal możesz napisać to w bardzo zwarty sposób:
int *sieve = NEW(int, 1);
i będzie się kompilować dla C i C ++.
Z Wikipedii :
Zalety odlewania
Uwzględnienie rzutowania może pozwolić programowi lub funkcji w C na kompilację w języku C ++.
Obsada pozwala na wersje malloc sprzed 1989 roku, które pierwotnie zwróciły char *.
Rzutowanie może pomóc deweloperowi zidentyfikować niespójności w ustalaniu wielkości typu, jeśli typ wskaźnika docelowego ulegnie zmianie, szczególnie jeśli wskaźnik jest zadeklarowany daleko od wywołania malloc () (chociaż nowoczesne kompilatory i analizatory statyczne mogą ostrzegać o takim zachowaniu bez wymagania rzutowania).
Wady odlewania
Zgodnie ze standardem ANSI C odlew jest zbędny.
Dodanie rzutowania może maskować niepowodzenie dołączenia nagłówka stdlib.h , w którym znajduje się prototyp malloc. W przypadku braku prototypu dla malloc, standard wymaga, aby kompilator C założył, że malloc zwraca int. Jeśli nie ma rzutowania, pojawia się ostrzeżenie, gdy ta liczba całkowita jest przypisana do wskaźnika; jednak przy obsadzie to ostrzeżenie nie jest wyświetlane, ukrywając błąd. W przypadku niektórych architektur i modeli danych (takich jak LP64 w systemach 64-bitowych, gdzie long i wskaźniki są 64-bitowe, a int to 32-bitowe), ten błąd może w rzeczywistości skutkować niezdefiniowanym zachowaniem, ponieważ niejawnie zadeklarowany malloc zwraca 32- wartość bitu, podczas gdy faktycznie zdefiniowana funkcja zwraca wartość 64-bitową. W zależności od konwencji wywoływania i układu pamięci może to spowodować rozwalenie stosu. Jest mniej prawdopodobne, że ten problem pozostanie niezauważony w nowoczesnych kompilatorach, ponieważ jednolicie generują one ostrzeżenia o użyciu niezadeklarowanej funkcji, więc ostrzeżenie będzie nadal się pojawiać. Na przykład, domyślnym zachowaniem GCC jest wyświetlenie ostrzeżenia, które brzmi „niekompatybilna niejawna deklaracja funkcji wbudowanej” niezależnie od tego, czy rzutowanie jest obecne, czy nie.
Jeśli typ wskaźnika zostanie zmieniony przy jego deklaracji, może również zajść potrzeba zmiany wszystkich linii, w których jest wywoływany i rzutowany malloc.
Chociaż malloc bez rzutowania jest preferowaną metodą i większość doświadczonych programistów ją wybiera , powinieneś użyć tego, co lubisz, mając świadomość problemów.
tj .: Jeśli chcesz skompilować program w C jako C ++ (chociaż jest to odrębny język), musisz rzutować wynik użycia malloc
.
W C możesz niejawnie przekonwertować void
wskaźnik na dowolny inny rodzaj wskaźnika, więc rzutowanie nie jest konieczne. Korzystanie z niej może sugerować przypadkowemu obserwatorowi, że jest jakiś powód, dla którego jest potrzebny, co może wprowadzać w błąd.
Nie rzutujesz wyniku malloc
, ponieważ powoduje to niepotrzebny bałagan w kodzie.
Najczęstszym powodem, dla którego ludzie rzucają wynik, malloc
jest to, że nie są pewni, jak działa język C. To znak ostrzegawczy: jeśli nie wiesz, jak działa dany mechanizm językowy, nie zgaduj. Sprawdź to lub zapytaj w Stack Overflow.
Kilka komentarzy:
Wskaźnik void można przekonwertować na / z dowolnego innego typu wskaźnika bez jawnego rzutowania (C11 6.3.2.3 i 6.5.16.1).
C ++ nie pozwoli jednak na niejawne rzutowanie między
void*
innym typem wskaźnika. Więc w C ++ rzutowanie byłoby poprawne. Ale jeśli programujesz w C ++, powinieneś używać,new
a niemalloc()
. I nigdy nie powinieneś kompilować kodu w C przy użyciu kompilatora C ++.Jeśli chcesz obsługiwać zarówno C, jak i C ++ z tym samym kodem źródłowym, użyj przełączników kompilatora, aby zaznaczyć różnice. Nie próbuj nasycać obu standardów językowych tym samym kodem, ponieważ nie są one zgodne.
Jeśli kompilator C nie może znaleźć funkcji, ponieważ zapomniałeś dołączyć nagłówka, zostanie wyświetlony błąd kompilatora / konsolidatora. Więc jeśli zapomniałeś załączyć,
<stdlib.h>
że to nic wielkiego, nie będziesz w stanie zbudować swojego programu.W starożytnych kompilatorach, które stosują się do wersji normy, która ma więcej niż 25 lat, zapomnienie o uwzględnieniu
<stdlib.h>
spowodowałoby niebezpieczne zachowanie. Ponieważ w tym starożytnym standardzie funkcje bez widocznego prototypu niejawnie konwertowały zwracany typ naint
. Rzutowanie wyniku zmalloc
jawnie spowodowałoby ukrycie tego błędu.Ale to naprawdę nie jest problem. Nie używasz 25-letniego komputera, więc dlaczego miałbyś używać 25-letniego kompilatora?
W C otrzymujesz niejawną konwersję z void *
do dowolnego innego wskaźnika (danych).
Rzucanie wartości zwracanej przez malloc()
nie jest teraz konieczne, ale chciałbym dodać jeden punkt, na który nikt nie zwrócił uwagi:
W dawnych czasach, to znaczy przed ANSI C oferuje void *
jako rodzajowy typu wskaźników, char *
to typ dla takiego wykorzystania. W takim przypadku rzutowanie może wyłączyć ostrzeżenia kompilatora.
Źródła: C FAQ
Dodając tylko moje doświadczenie, studiując inżynierię komputerową, widzę, że dwóch lub trzech profesorów, których widziałem piszących w C, zawsze obsadzało malloc, jednak ten, o którego poprosiłem (z ogromnym CV i zrozumieniem C), powiedział mi, że jest to absolutnie niepotrzebne, ale Kiedyś był absolutnie konkretny i wprowadzał uczniów w mentalność bycia absolutnie konkretnym. Zasadniczo rzutowanie niczego nie zmieni w sposobie działania, robi dokładnie to, co mówi, alokuje pamięć, a rzutowanie nie wpływa na to, dostajesz tę samą pamięć, a nawet jeśli przez pomyłkę rzucisz ją na coś innego (i jakoś ominiesz kompilator błędy) C uzyska do niego dostęp w ten sam sposób.
Edycja: Casting ma pewien punkt. Kiedy używasz notacji tablicowej, wygenerowany kod musi wiedzieć, ile miejsc pamięci musi przejść, aby dotrzeć do początku następnego elementu, osiąga się to poprzez rzutowanie. W ten sposób wiesz, że dla double idziesz 8 bajtów do przodu, podczas gdy dla int idziesz 4 i tak dalej. Dlatego nie ma znaczenia, jeśli używasz notacji wskaźnikowej, w notacji tablicowej staje się to konieczne.
Rzutowanie wyników nie jest obowiązkowe malloc
, ponieważ zwraca void*
, a a void*
można wskazać na dowolny typ danych.
Oto, co mówi Podręcznik referencyjny biblioteki GNU C :
Możesz zapisać wynik
malloc
w dowolnej zmiennej wskaźnikowej bez rzutowania, ponieważ ISO C automatycznie konwertuje typvoid *
na inny typ wskaźnika, gdy jest to konieczne. Ale rzutowanie jest konieczne w kontekstach innych niż operatory przypisania lub jeśli chcesz, aby kod działał w tradycyjnym C.
I rzeczywiście norma ISO C11 (p347) tak mówi:
Wskaźnik zwracany, jeśli alokacja się powiedzie, jest odpowiednio wyrównany, aby można go było przypisać do wskaźnika do dowolnego typu obiektu z podstawowym wymogiem wyrównania, a następnie użyć go do uzyskania dostępu do takiego obiektu lub tablicy takich obiektów w przydzielonej przestrzeni (do miejsce jest jawnie cofnięte)
Wskaźnik void jest ogólnym wskaźnikiem obiektu, a C obsługuje niejawną konwersję z typu void wskaźnika do innych typów, więc nie ma potrzeby jawnego rzutowania go na typ.
Jeśli jednak chcesz, aby ten sam kod działał idealnie kompatybilnie na platformie C ++, która nie obsługuje niejawnej konwersji, musisz wykonać rzutowanie typów, więc wszystko zależy od użyteczności.
Zwracany typ to void *, który można rzutować na żądany typ wskaźnika danych, aby można go było wyłuskać.
To zależy od języka programowania i kompilatora. Jeśli używasz malloc
w C, nie ma potrzeby wpisywania rzutowania, ponieważ automatycznie wypisze rzut. Jeśli jednak używasz C ++, powinieneś wpisać rzutowanie, ponieważ malloc
zwróci void*
typ.
W języku C wskaźnik void można przypisać do dowolnego wskaźnika, dlatego nie należy używać rzutowania typu. Jeśli chcesz przydzielić "bezpieczny typ", mogę polecić następujące funkcje makr, których zawsze używam w moich projektach C:
#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)
Mając te na miejscu, możesz po prostu powiedzieć
NEW_ARRAY(sieve, length);
W przypadku tablic niedynamicznych trzecie niezbędne makro funkcji to
#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])
co sprawia, że pętle tablicowe są bezpieczniejsze i wygodniejsze:
int i, a[100];
for (i = 0; i < LEN(a); i++) {
...
}
Ludzie przyzwyczajeni do GCC i Clang są zepsute. Nie wszystko tam jest takie dobre.
Przez lata byłem przerażony zdumiewająco przestarzałymi kompilatorami, których musiałem używać. Często firmy i menedżerowie przyjmują ultra-konserwatywne podejście do zmiany kompilatorów i nawet nie będą testować, czy nowy kompilator (z lepszą zgodnością ze standardami i optymalizacją kodu) będzie działał w ich systemie. Praktyczna rzeczywistość dla pracujących programistów jest taka, że kiedy kodujesz, musisz zakryć swoje bazy i, niestety, rzucanie mallocs jest dobrym nawykiem, jeśli nie możesz kontrolować, który kompilator może zostać zastosowany do twojego kodu.
Sugerowałbym również, że wiele organizacji stosuje własny standard kodowania i że powinna to być metoda stosowana przez ludzi, jeśli zostanie zdefiniowana. Wobec braku wyraźnych wskazówek skłaniam się ku najprawdopodobniej wszędzie kompilacji, a nie niewolniczym przestrzeganiu standardów.
Argument, że nie jest to konieczne w obecnych standardach, jest całkiem słuszny. Ale ten argument pomija praktyczne aspekty prawdziwego świata. Nie kodujemy w świecie, w którym rządzą wyłącznie dzisiejsze standardy, ale praktyczność tego, co nazywam „polem rzeczywistości lokalnego zarządzania”. I to jest wygięte i pokręcone bardziej niż kiedykolwiek była czasoprzestrzeń. :-)
YMMV.
Myślę o rzucaniu Malloca jako operacji obronnej. Nie ładne, nie idealne, ale ogólnie bezpieczne. (Szczerze mówiąc, jeśli nie włączyłeś stdlib.h, masz o wiele więcej problemów niż rzucanie malloc!).
Nie, nie rzucasz wyniku malloc()
.
Ogólnie rzecz biorąc, nie przesyła się do ani zvoid *
.
Typowym powodem, dla którego tego nie robisz, jest to, że niepowodzenie #include <stdlib.h>
może pozostać niezauważone. To już nie jest problem od dłuższego czasu, ponieważ C99 uczynił niejawne deklaracje funkcji nielegalnymi, więc jeśli twój kompilator jest zgodny co najmniej z C99, otrzymasz komunikat diagnostyczny.
Ale jest znacznie silniejszy powód, aby nie wprowadzać niepotrzebnych rzutów wskaźnika:
W języku C rzutowanie wskaźnika jest prawie zawsze błędem . Dzieje się tak z powodu następującej reguły ( §6.5 p7 w N1570, najnowszy projekt dla C11):
Obiekt powinien mieć dostęp do swojej przechowywanej wartości tylko za pomocą wyrażenia l-wartości, które ma jeden z następujących typów:
- typ zgodny z efektywnym typem obiektu,
- kwalifikowana wersja typu zgodna z efektywnym typem obiektu,
- typ będący typem ze znakiem lub bez znaku odpowiadającym efektywnemu typowi obiektu,
- typ, który jest typem ze znakiem lub bez znaku odpowiadającym kwalifikowanej wersji efektywnego typu obiektu,
- typ agregacyjny lub sumaryczny, który zawiera jeden wyżej wymienionych typów wśród swoich członków (w tym, rekurencyjnie, członek subagregatu lub zawartej unii), lub
- typ znakowy.
Jest to również znane jako ścisła reguła aliasingu . Zatem poniższy kod jest niezdefiniowanym zachowaniem :
long x = 5;
double *p = (double *)&x;
double y = *p;
A czasami zaskakujące jest również:
struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;
Czasami nie trzeba wskaźniki obsady, ale biorąc pod uwagę ścisłe reguły aliasingu , trzeba być bardzo ostrożnym z nim. Zatem każde wystąpienie rzutowania wskaźnika w twoim kodzie jest miejscem, w którym musisz dwukrotnie sprawdzić jego poprawność . Dlatego nigdy nie piszesz niepotrzebnego rzutowania wskaźnika.
tl; dr
W skrócie: ponieważ w C każde wystąpienie rzutowania wskaźnika powinno wywołać czerwoną flagę dla kodu wymagającego szczególnej uwagi, nigdy nie powinieneś pisać niepotrzebnych rzutów wskaźników.
Uwagi dodatkowe:
Istnieją przypadki, w których faktycznie potrzebujesz rzutowania
void *
, np. Jeśli chcesz wydrukować wskaźnik:int x = 5; printf("%p\n", (void *)&x);
Rzutowanie jest tutaj konieczne, ponieważ
printf()
jest funkcją wariadyczną, więc niejawne konwersje nie działają.W C ++ sytuacja jest inna. Rzutowanie typów wskaźników jest dość powszechne (i poprawne) w przypadku obiektów klas pochodnych. Dlatego ma sens, że w C ++ konwersja na iz nie
void *
jest niejawna. C ++ ma cały zestaw różnych odmian rzutowania.
Umieściłem w obsadzie po prostu, aby pokazać dezaprobatę dla brzydkiej dziury w systemie typów, która pozwala na kompilację kodu takiego jak poniższy fragment bez diagnostyki, mimo że żadne rzutowania nie są używane do wywołania złej konwersji:
double d;
void *p = &d;
int *q = p;
Chciałbym, żeby to nie istniało (i nie ma tego w C ++), więc rzucam. Reprezentuje mój gust i moją politykę programową. Nie tylko rzucam wskazówkę, ale skutecznie głosuję i wyrzucam demony głupoty . Jeśli faktycznie nie mogę wyrzucić głupoty , pozwólcie mi przynajmniej wyrazić tę chęć gestem protestu.
W rzeczywistości dobrą praktyką jest zapakowanie malloc
(i przyjaciół) funkcji, które powracają unsigned char *
, i zasadniczo nigdy ich nie używać void *
w kodzie. Jeśli potrzebujesz ogólnego wskaźnika do dowolnego obiektu, użyj char *
lub unsigned char *
, i rzuć w obu kierunkach. Jedynym relaksem, na który można sobie pozwolić, jest użycie funkcji takich jak rzutowanie memset
i memcpy
bez.
Na temat odlewów i kompatybilności C ++, jeśli napisać kod tak, że kompiluje jako ++ (w takim przypadku zarówno C i C muszą rzutować wartości zwracanej malloc
podczas przypisywania go do czegoś innego niż void *
), można zrobić bardzo pomocny rzecz dla siebie: możesz użyć makr do rzutowania, które przekładają się na rzutowanie w stylu C ++ podczas kompilacji jako C ++, ale zredukuj do rzutowania C podczas kompilacji jako C:
/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif
Jeśli zastosujesz się do tych makr, proste grep
przeszukanie bazy kodu pod kątem tych identyfikatorów pokaże Ci, gdzie znajdują się wszystkie Twoje rzuty, dzięki czemu możesz sprawdzić, czy którekolwiek z nich są nieprawidłowe.
Następnie, jeśli będziesz regularnie kompilować kod w C ++, wymusi to użycie odpowiedniego rzutowania. Na przykład, jeśli użyjesz strip_qual
tylko do usunięcia const
lub volatile
, ale program zmieni się w taki sposób, że jest teraz zaangażowana konwersja typu, otrzymasz diagnostykę i będziesz musiał użyć kombinacji rzutów, aby uzyskać żądaną konwersję.
Aby pomóc Ci w przestrzeganiu tych makr, kompilator GNU C ++ (nie C!) Ma piękną funkcję: opcjonalną diagnostykę, która jest tworzona dla wszystkich wystąpień rzutów w stylu C.
-Wold-style-cast (tylko C ++ i Objective-C ++) Ostrzegaj, jeśli jest używane rzutowanie w starym stylu (w stylu C) na typ nie void w programie C ++. Rzuty w nowym stylu (dynamic_cast, static_cast, reinterpret_cast i const_cast) są mniej podatne na ataki na niezamierzone efekty i znacznie łatwiejsze do wyszukania.
Jeśli Twój kod C kompiluje się jako C ++, możesz użyć tej -Wold-style-cast
opcji, aby znaleźć wszystkie wystąpienia (type)
składni rzutowania, które mogą wkradać się do kodu, i kontynuować te diagnostyki, zastępując je odpowiednim wyborem spośród powyższych makr (lub połączenie, jeśli to konieczne).
Takie traktowanie konwersji jest największym pojedynczym, samodzielnym technicznym uzasadnieniem pracy w „czystym C”: połączonym dialekcie C i C ++, co z kolei technicznie uzasadnia rzutowanie wartości zwracanej malloc
.
Wolę odlewać, ale nie ręcznie. Moim ulubionym jest używanie g_new
i g_new0
makra z glib. Jeśli glib nie jest używany, dodałbym podobne makra. Te makra zmniejszają powielanie kodu bez uszczerbku dla bezpieczeństwa typów. Jeśli otrzymasz nieprawidłowy typ, otrzymasz niejawne rzutowanie między wskaźnikami, które nie są puste, co spowoduje ostrzeżenie (błąd w C ++). Jeśli zapomnisz dołączyć nagłówek, który definiuje g_new
i g_new0
, pojawi się błąd. g_new
i g_new0
obie przyjmują te same argumenty, w przeciwieństwie do malloc
tego pobiera mniej argumentów niż calloc
. Po prostu dodaj, 0
aby uzyskać zerową pamięć zainicjowaną. Kod można skompilować bez zmian za pomocą kompilatora C ++.
Najlepszą rzeczą do zrobienia podczas programowania w C, kiedy tylko jest to możliwe:
- Skompiluj swój program za pomocą kompilatora C z włączonymi wszystkimi ostrzeżeniami
-Wall
i napraw wszystkie błędy i ostrzeżenia - Upewnij się, że nie ma zmiennych zadeklarowanych jako
auto
- Następnie skompiluj go za pomocą kompilatora C ++ z
-Wall
i-std=c++11
. Napraw wszystkie błędy i ostrzeżenia. - Teraz skompiluj ponownie przy użyciu kompilatora C. Twój program powinien teraz kompilować się bez żadnego ostrzeżenia i zawierać mniej błędów.
Ta procedura pozwala wykorzystać ścisłe sprawdzanie typu C ++, zmniejszając w ten sposób liczbę błędów. W szczególności ta procedura zmusza Cię do włączenia stdlib.h
lub otrzymasz
malloc
nie została zadeklarowana w tym zakresie
a także zmusza cię do rzucenia wyniku malloc
lub otrzymasz
nieprawidłowa konwersja z
void*
naT*
lub jaki jest twój typ celu.
Jedyne korzyści płynące z pisania w C zamiast C ++ to
- C ma dobrze określony ABI
- C ++ może generować więcej kodu [wyjątki, RTTI, szablony, polimorfizm w czasie wykonywania ]
Zauważ, że drugie wady powinny w idealnym przypadku zniknąć, gdy użyjemy podzbioru wspólnego dla C razem ze statyczną cechą polimorficzną.
Dla tych, dla których surowe reguły C ++ są niewygodne, możemy użyć funkcji C ++ 11 z wywnioskowanym typem
auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...
Rzutowanie jest przeznaczone tylko dla C ++, a nie dla C. Jeśli używasz kompilatora C ++, lepiej zmień go na kompilator C.
Rzutowanie malloc jest niepotrzebne w C, ale obowiązkowe w C ++.
Przesyłanie jest niepotrzebne w C z powodu:
void *
jest automatycznie i bezpiecznie promowany do dowolnego innego typu wskaźnika w przypadku C.- Może ukryć błąd, jeśli zapomnisz dołączyć
<stdlib.h>
. Może to powodować awarie. - Jeśli wskaźniki i liczby całkowite mają różną wielkość, ukrywasz ostrzeżenie przez rzutowanie i możesz utracić bity zwróconego adresu.
- Jeśli typ wskaźnika zostanie zmieniony przy jego deklaracji, może zajść potrzeba zmiany wszystkich wierszy, w których
malloc
jest wywoływany i rzutowany.
Z drugiej strony przesyłanie może zwiększyć przenośność programu. tj. umożliwia programowi lub funkcji w C kompilację w języku C ++.
Koncepcja void pointer polega na tym, że można go rzutować na dowolny typ danych, dlatego malloc zwraca void. Musisz także pamiętać o automatycznym rzutowaniu. Dlatego rzutowanie wskaźnika nie jest obowiązkowe, chociaż musisz to zrobić. Pomaga w utrzymaniu czystości kodu i ułatwia debugowanie
Wskaźnik void jest wskaźnikiem ogólnym, a C obsługuje niejawną konwersję z typu wskaźnika void do innych typów, więc nie ma potrzeby jawnego rzutowania go na typ.
Jeśli jednak chcesz, aby ten sam kod działał idealnie kompatybilnie na platformie C ++, która nie obsługuje niejawnej konwersji, musisz wykonać rzutowanie typów, więc wszystko zależy od użyteczności.
Jak stwierdzono inaczej, nie jest to potrzebne w C, ale w C ++.
Uwzględnienie rzutowania może pozwolić programowi lub funkcji w C na kompilację w języku C ++.
W C jest to niepotrzebne, ponieważ void * jest automatycznie i bezpiecznie promowane do dowolnego innego typu wskaźnika.
Ale jeśli wtedy rzucisz, może to ukryć błąd, jeśli zapomnisz dołączyć stdlib.h . Może to powodować awarie (lub, co gorsza, spowodować awarię dopiero znacznie później w zupełnie innej części kodu).
Ponieważ stdlib.h zawiera prototyp dla malloc został znaleziony. W przypadku braku prototypu dla malloc, standard wymaga, aby kompilator C założył, że malloc zwraca int. Jeśli nie ma rzutowania, pojawia się ostrzeżenie, gdy ta liczba całkowita jest przypisana do wskaźnika; jednak przy obsadzie to ostrzeżenie nie jest wyświetlane, ukrywając błąd.
To pytanie jest przedmiotem nadużyć opartych na opiniach.
Czasami zauważam takie komentarze:
Nie rzucaj wyniku malloc
lub
Dlaczego nie rzucisz wyniku malloc
w pytaniach, w których OP używa rzutowania. Same komentarze zawierają hiperłącze do tego pytania.
Jest to również w jakikolwiek sposób niewłaściwe i niepoprawne. Nie ma dobra ani zła, kiedy jest to naprawdę kwestia własnego stylu kodowania.
Dlaczego to się dzieje?
Ma dwa powody:
To pytanie jest rzeczywiście oparte na opiniach. Technicznie rzecz biorąc, kwestia ta powinna była zostać zamknięta lata temu jako oparta na opiniach. Pytanie typu „ Czy ja ” lub „ Nie mam ” lub równoważne pytanie „ Powinienem ” lub „ Nie powinienem ”, po prostu nie możesz odpowiedzieć skoncentrowaną bez własnej postawy. Jednym z powodów zamknięcia pytania jest to, że „może ono prowadzić do odpowiedzi opartych na opiniach”, jak to dobrze pokazano tutaj.
Wiele odpowiedzi (w tym najbardziej widocznej i zaakceptowanych odpowiedź na @unwind ) są całkowicie lub prawie całkowicie na podstawie opinii (fe tajemnicza „bałagan”, który zostanie dodany do kodu jeśli nie odlewanie lub powtarzać się będzie źle) i pokaz wyraźna i skupiona tendencja do pomijania obsady. Sprzeczają się o redundancję rzutowania z jednej strony, ale także, co gorsza, argumentują, że rozwiązują błąd spowodowany błędem / niepowodzeniem samego programowania - a nie,
#include <stdlib.h>
jeśli ktoś chce go użyćmalloc()
.
Chcę przedstawić prawdziwy pogląd na niektóre omówione kwestie, nie wyrażając mojej osobistej opinii. Należy zwrócić szczególną uwagę na kilka punktów:
Na tak bardzo podatne na wpadnięcie pytanie, trzeba odpowiedzieć neutralnie za i przeciw. Nie tylko wady i zalety.
Dobry przegląd zalet i wad znajduje się w tej odpowiedzi:
https://stackoverflow.com/a/33047365/12139179
(Osobiście uważam to za najlepszą jak dotąd odpowiedź).
Jednym z powodów, które można napotkać, aby uzasadnić pominięcie rzutowania, jest to, że rzutowanie może ukrywać błąd.
Jeśli ktoś używa niejawnej zadeklarowanej,
malloc()
która zwracaint
(niejawne funkcje zniknęły ze standardu od C99) isizeof(int) != sizeof(int*)
, jak pokazano w tym pytaniuDlaczego ten kod segfaulta w architekturze 64-bitowej, ale działa dobrze na 32-bitowej?
obsada ukrywała błąd.
Chociaż to prawda, pokazuje tylko połowę historii, ponieważ pominięcie obsady byłoby tylko krokiem naprzód w przypadku jeszcze większego błędu - nie dotyczy
stdlib.h
to użyciamalloc()
.To nigdy nie będzie poważny problem, jeśli
Użyj kompilatora zgodnego z C99 lub nowszym (co jest zalecane i powinno być obowiązkowe) i
Nie są tak nieobecne, aby zapomnieć o dołączeniu
stdlib.h
, kiedy chcesz użyćmalloc()
w swoim kodzie, co samo w sobie jest ogromnym błędem.
Niektórzy spierają się o zgodność kodu C z C ++, ponieważ rzutowanie jest wymagane w C ++.
Po pierwsze, ogólnie rzecz biorąc: kompilowanie kodu w C za pomocą kompilatora C ++ nie jest dobrą praktyką.
C i C ++ to w rzeczywistości dwa zupełnie różne języki o różnej semantyce.
Ale jeśli naprawdę chcesz / potrzebujesz, aby kod C był zgodny z C ++ i odwrotnie, użyj przełączników kompilatora zamiast dowolnego rzutowania.
Ponieważ obsada jest z tendencją uznawana za zbędną lub nawet szkodliwą, chcę skupić się na tych pytaniach, które dają dobre powody, dla których rzucanie może być przydatne, a nawet konieczne:
https://stackoverflow.com/a/34094068/12139179
https://stackoverflow.com/a/36297486/12139179
https://stackoverflow.com/a/33044300/12139179
- Rzutowanie może być niekorzystne, gdy kod, odpowiednio typ przypisanego wskaźnika (a wraz z nim typ rzutowania), ulegają zmianie, chociaż w większości przypadków jest to mało prawdopodobne. Wtedy musiałbyś również utrzymywać / zmieniać wszystkie rzutowania, a jeśli masz kilka tysięcy wywołań funkcji zarządzania pamięcią w swoim kodzie, może to naprawdę podsumować i zmniejszyć wydajność konserwacji.
Podsumowanie:
Faktem jest, że rzutowanie jest redundantne zgodnie ze standardem C (już od ANSI-C (C89 / C90)), jeśli przypisany wskaźnik wskazuje na obiekt o podstawowym wymaganiu wyrównania (który obejmuje większość wszystkich obiektów).
Nie musisz wykonywać rzutowania, ponieważ wskaźnik jest automatycznie wyrównany w tym przypadku:
„Kolejność i ciągłość pamięci przydzielanej przez kolejne wywołania funkcji aligned_alloc, calloc, malloc i realloc są nieokreślone. Wskaźnik zwracany, jeśli alokacja się powiedzie, jest odpowiednio wyrównany, tak że można go przypisać do wskaźnika do dowolnego typu obiektu z podstawowy wymóg wyrównania, a następnie używany do uzyskiwania dostępu do takiego obiektu lub tablicy takich obiektów w przydzielonej przestrzeni (do momentu jawnego zwolnienia miejsca). "
Źródło: C18, § 7.22.3 / 1
„ Podstawowe wyrównanie to ważne wyrównanie mniejsze lub równe
_Alignof (max_align_t)
. Podstawowe wyrównanie jest wspierane przez implementację dla obiektów o wszystkich okresach przechowywania. Wymogi wyrównania następujących typów są podstawowymi wyrównaniami:- wszystkie podstawowe typy atomowe, kwalifikowane lub niekwalifikowane;
- wszystkie niepodzielne, kwalifikowane lub niekwalifikowane typy wyliczeniowe;
- wszystkie typy wskaźników atomowych, kwalifikowanych lub niekwalifikowanych;
- wszystkie typy tablic, których typ elementu ma podstawowe wymaganie wyrównania; 57)
- wszystkie typy określone w punkcie 7 jako kompletne typy obiektów;
- wszystkie typy struktur lub unii, których wszystkie elementy mają typy z podstawowymi wymaganiami dotyczącymi wyrównania i żaden z nich nie ma specyfikatora wyrównania określającego wyrównanie, które nie jest wyrównaniem podstawowym.
- Jak określono w 6.2.1, późniejsza deklaracja może ukryć poprzednią deklarację. "
Źródło: C18, § 6.2.8 / 2
Jeśli jednak przydzielisz pamięć dla obiektu zdefiniowanego w implementacji z wymaganiem rozszerzonego wyrównania, rzutowanie będzie potrzebne.
Rozszerzony wyrównanie jest reprezentowany przez większej zgodności niż
_Alignof (max_align_t)
. Jest zdefiniowane w implementacji, czy jakiekolwiek rozszerzone wyrównania są obsługiwane i okresy przechowywania, dla których są obsługiwane. Typ z rozszerzonym wymaganiem wyrównania jest typem nadmiernie wyrównanym.58)Źródło. C18, § 6.2.8 / 3
Wszystko inne jest kwestią konkretnego przypadku użycia i własnej opinii.
Uważaj, jak się kształcisz.
Zalecam uważne przeczytanie wszystkich dotychczas udzielonych odpowiedzi (wraz z komentarzami, które mogą wskazywać na niepowodzenie), a następnie zbudowanie własnej opinii, jeśli nie oceniasz wyniku malloc()
w konkretnym przypadku.
Proszę zanotować:
Nie ma dobrej i złej odpowiedzi na to pytanie. Jest to kwestia stylu i to Ty sam decydujesz, który sposób wybierzesz (jeśli oczywiście nie zmusza Cię do tego wykształcenie lub praca). Pamiętaj o tym i nie daj się oszukać .
Ostatnia uwaga: głosowałem za zamknięciem ostatnio tej kwestii jako opartej na opinii, która jest rzeczywiście potrzebna od lat. Jeśli masz przywilej Zamknij / Ponownie otwórz, chciałbym Cię do tego zaprosić.
Dla mnie, podsumowanie jest takie, że rzucanie malloc
w C nie jest absolutnie konieczne, ale jeśli jednak rzucisz, nie wpłynie to, malloc
ponieważ malloc
nadal przydzieli ci twoje błogosławione miejsce w pamięci. Innym przykładem jest powód lub jeden z powodów, dla których ludzie dokonują rzutowania, a to ma na celu umożliwienie im kompilacji tego samego programu w C lub C ++.
Mogą istnieć inne powody, ale inne powody, prawie na pewno, sprawią, że wcześniej czy później wpadniesz w poważne kłopoty.
Możesz, ale nie musisz, rzutować w C. Musisz rzutować, jeśli ten kod jest skompilowany jako C ++.