W C, dlaczego niektórzy ludzie rzucają wskaźnik przed jego zwolnieniem?

167

Pracuję na starym kodzie i prawie każde wywołanie free () używa rzutowania na swoim argumencie. Na przykład,

free((float *)velocity);
free((float *)acceleration);
free((char *)label);

gdzie każdy wskaźnik jest odpowiedniego (i pasującego) typu. Nie widzę w tym sensu. To bardzo stary kod, więc zastanawiam się, czy to kwestia K&R. Jeśli tak, to faktycznie chciałbym wspierać stare kompilatory, które tego wymagały, więc nie chcę ich usuwać.

Czy istnieje techniczny powód, aby używać tych odlewów? Nie widzę nawet pragmatycznego powodu, aby ich używać. Jaki jest sens przypominania sobie o typie danych tuż przed ich zwolnieniem?

EDYCJA: To pytanie nie jest powtórzeniem drugiego pytania. Drugie pytanie to szczególny przypadek tego pytania, co moim zdaniem jest oczywiste, gdyby bliscy wyborcy przeczytali wszystkie odpowiedzi.

Kolofon: Daję „const odpowiedź” znacznik wyboru, ponieważ jest to prawdziwy prawdziwy powód, dla którego może być to konieczne; Jednak odpowiedź o tym, że jest to niestandardowa wersja przed ANSI C (przynajmniej wśród niektórych programistów) wydaje się być powodem, dla którego został użyty w moim przypadku. Wiele osób tutaj ma dobre strony. Dziękuję za Twój wkład.

Dr Osoba Osoba II
źródło
13
„Jaki jest sens przypominania sobie o typie danych tuż przed ich zwolnieniem?” Może żeby wiedzieć, ile pamięci zostanie uwolnione?
m0skit0
12
@Codor Kompilator nie usuwa alokacji, ale system operacyjny.
m0skit0
20
@ m0skit0 "Może chcesz wiedzieć, ile pamięci zostanie zwolnione?" Wpisz nie jest konieczne, aby wiedzieć, ile za darmo. Tylko z tego powodu jest to złe kodowanie.
user694733
9
@ m0skit0 Rzutowanie ze względu na czytelność jest zawsze złym kodowaniem, ponieważ rzutowanie zmienia sposób interpretacji typów i może ukryć poważne błędy. Kiedy potrzebna jest czytelność, komentarze są lepsze.
user694733
66
W dawnych czasach, kiedy dinozaury chodziły po ziemi i pisały książki o programowaniu, myślę, że nie było void*wcześniejszej wersji C, ale tylko char*. Więc jeśli twoje odkrycia archeologiczne ujawnią kod rzucający parametr na free (), uważam, że musi to być albo z tego okresu, albo napisany przez stworzenie z tego czasu. Nie mogę znaleźć dla tego żadnego źródła, więc powstrzymam się od odpowiedzi.
Lundin

Odpowiedzi:

171

Rzutowanie może być wymagane w celu rozwiązania ostrzeżeń kompilatora, jeśli wskaźniki są const. Oto przykład kodu, który powoduje ostrzeżenie bez rzutowania argumentu free:

const float* velocity = malloc(2*sizeof(float));
free(velocity);

Kompilator (gcc 4.8.3) mówi:

main.c: In function main’:
main.c:9:5: warning: passing argument 1 of free discards const qualifier from pointer target type [enabled by default]
     free(velocity);
     ^
In file included from main.c:2:0:
/usr/include/stdlib.h:482:13: note: expected void *’ but argument is of type const float *’
 extern void free (void *__ptr) __THROW;

Jeśli używasz free((float*) velocity);kompilatora, przestań narzekać.

Manos Nikolaidis
źródło
2
@ m0skit0 to nie wyjaśnia, dlaczego ktoś miał przesyłać do float*przed zwolnieniem. Próbowałem free((void *)velocity);z gcc 4.8.3. Oczywiście to nie zadziała ze starożytnym kompilatorem
Manos Nikolaidis
54
Ale dlaczego miałbyś dynamicznie przydzielać stałą pamięć? Nigdy nie mogłeś tego użyć!
Nils_M
33
@Nils_M to uproszczony przykład. To, co zrobiłem w rzeczywistym kodzie funkcji, to przydzielenie pamięci innej niż stała, przypisanie wartości, rzutowanie na wskaźnik do stałej i zwrócenie jej. Teraz jest wskaźnik do wstępnie przypisanej pamięci const, którą ktoś musi zwolnić.
Manos Nikolaidis
2
Przykład : „Te podprogramy zwracają ciąg znaków w nowej pamięci malloc, wskazywany przez * stringValueP, który musisz ostatecznie zwolnić. Czasami funkcja systemu operacyjnego, której używasz do zwalniania pamięci, jest deklarowana jako argument jako wskaźnik do czegoś niestałego, więc ponieważ * stringValueP jest wskaźnikiem do stałej. ”
Carsten S
3
Błędne, jeśli funkcja przyjmuje const char *pjako argument, a następnie uwalnia ją, poprawna rzeczą do zrobienia jest nie do obsady paby char*przed wywołaniem darmo. Przede wszystkim nie należy go deklarować jako zajmujący const char *p, ponieważ modyfikuje *p i powinien zostać odpowiednio zadeklarowany. (A jeśli przyjmuje wskaźnik const zamiast wskaźnika do const, int *const pnie musisz rzucać, ponieważ jest to faktycznie legalne i działa dobrze bez rzutowania.)
Ray
59

Wcześniejszy standard C miał nie void*tylko char*, więc trzeba było rzutować wszystkie przekazane parametry. Jeśli natkniesz się na starożytny kod C, możesz znaleźć takie odlewy.

Podobne pytanie z referencjami .

Kiedy pierwszy standard C został wydany, prototypy dla malloc i za darmo zmieniły się z konieczności char*na te void*, które mają do dziś.

I oczywiście w standardowym C takie odlewy są zbędne i po prostu szkodzą czytelności.

Lundin
źródło
23
Ale dlaczego miałbyś rzutować argument freena ten sam typ, który już jest?
jwodder
4
@chux Problem z pre-standardem jest po prostu taki: nie ma żadnych zobowiązań. Ludzie po prostu wskazywali na książkę K&R dotyczącą kanonu, ponieważ była to jedyna rzecz, jaką mieli. I jak widać na kilku przykładach w drugiej edycji K&R, sami K&R są zdezorientowani co do tego, jak freerzutowania parametru działają w standardzie C (nie trzeba rzutować). Nie czytałem pierwszej edycji, więc nie mogę powiedzieć, czy byli zdezorientowani również w czasach przedstandardowych lat 80.
Lundin
7
Pre-standard C nie miał void*, ale nie miał też prototypów funkcji, więc rzutowanie argumentu of freebyło nadal niepotrzebne nawet w K&R (zakładając, że wszystkie typy wskaźników danych używały tej samej reprezentacji).
Ian Abbott
6
Z wielu powodów podanych już w komentarzach nie sądzę, aby ta odpowiedź miała sens.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
4
Nie rozumiem, jak ta odpowiedź naprawdę odpowiadałaby na cokolwiek istotnego. Pierwotne pytanie dotyczy rzutów na inne typy, a nie tylko na char *. Jaki sens miałoby to w starszych kompilatorach bez void? Co osiągnęłyby takie rzuty?
AnT
34

Oto przykład, w którym free nie powiedzie się bez obsady:

volatile int* p = (volatile int*)malloc(5 * sizeof(int));
free(p);        // fail: warning C4090: 'function' : different 'volatile' qualifiers
free((int*)p);  // success :)
free((void*)p); // success :)

W C możesz otrzymać ostrzeżenie (otrzymałeś je w VS2012). W C ++ pojawi się błąd.

Pomijając rzadkie przypadki, odlewanie po prostu nadyma kod ...

Edycja: rzuciłem, aby void*nie int*pokazywać niepowodzenia. Będzie działać tak samo, jak int*zostanie przekonwertowany na void*niejawnie. Dodano int*kod.

egur
źródło
Zauważ, że w kodzie zamieszczonym w pytaniu rzutowanie nie jest do void *, ale do float *i char *. Te odlewy są nie tylko obce, są złe.
Andrew Henle
1
W rzeczywistości pytanie jest o czymś przeciwnym.
m0skit0
1
Nie rozumiem odpowiedzi; w jakim sensie by się free(p)nie udało? Czy dałoby to błąd kompilatora?
Codor
1
To są dobre punkty. To samo dotyczy constoczywiście wskaźników do kwalifikacji.
Lundin
2
volatileistnieje od czasu, gdy C został znormalizowany, jeśli nie dłużej. To było nie dodawać w C99.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
30

Stary powód: 1. Używając free((sometype*) ptr), kod jasno określa typ wskaźnika, który powinien być traktowany jako część free()wywołania. Jawne rzutowanie jest przydatne, gdy free()jest zastępowane przez (zrób to sam) DIY_free().

#define free(ptr) DIY_free(ptr, sizeof (*ptr))

A DIY_free()był (jest) sposobem, szczególnie w trybie debugowania, na wykonanie analizy zwalnianego wskaźnika w czasie wykonywania. Często łączy się to DIY_malloc()z dodaniem danych uwierzytelniających , globalnych liczników użycia pamięci itp. Moja grupa stosowała tę technikę przez lata, zanim pojawiły się bardziej nowoczesne narzędzia. To zobowiązało, że przedmiot, który ma zostać uwolniony, został rzucony do typu, który został pierwotnie przydzielony.

  1. Biorąc pod uwagę wiele godzin spędzonych na śledzeniu problemów z pamięcią itp., Drobne sztuczki, takie jak rzutowanie typu free'd, pomogłyby w wyszukiwaniu i zawężaniu debugowania.

Nowoczesne: unikanie consti volatileostrzeżenia zgodnie z odpowiedziami Manosa Nikolaidisa @ i @egur . Myśl Chciałbym zwrócić uwagę na skutki 3 kwalifikatorów : const, volatile, i restrict.

[edit] Dodano char * restrict *rp2za @R .. komentarzu

void free_test(const char *cp, volatile char *vp, char * restrict rp, 
    char * restrict *rp2) {
  free(cp);  // warning
  free(vp);  // warning
  free(rp);  // OK
  free(rp2);  // warning
}

int main(void) {
  free_test(0,0,0,0);
  return 0;
}
chux - Przywróć Monikę
źródło
3
restrictnie jest problemem tylko ze względu na to, gdzie jest umieszczony - wpływa na obiekt, a rpnie na wskazany typ. Gdybyś zamiast tego miał char *restrict *rp, miałoby to znaczenie.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
16

Oto kolejna alternatywna hipoteza.

Powiedziano nam, że program został napisany przed C89, co oznacza, że ​​nie może pracować nad jakimś rodzajem niezgodności z prototypem free, ponieważ nie tylko nie było czegoś takiego jak C89 constani void *przed C89, nie było czegoś takiego jak prototyp funkcji przed C89. stdlib.hsam w sobie był wymysłem komitetu. Gdyby nagłówki systemowe freew ogóle zadeklarowały , zrobiłyby to w ten sposób:

extern free();  /* no `void` return type either! */

Teraz kluczową kwestią jest to, że brak prototypów funkcji oznaczał, że kompilator nie sprawdzał typów argumentów . Zastosował domyślne promocje argumentów (te same, które nadal mają zastosowanie do wywołań funkcji wariadycznych) i to wszystko. Odpowiedzialność za ułożenie argumentów na każdej stronie telefonicznej zgodnie z oczekiwaniami odbiorcy spoczywa całkowicie na programiście.

Jednak to nadal nie oznacza, że ​​konieczne było rzucenie argumentu freena większość kompilatorów K&R. Funkcja taka jak

free_stuff(a, b, c)
    float *a;
    char *b;
    int *c;
{
    free(a);
    free(b);
    free(c);
}

powinien zostać poprawnie skompilowany. Myślę więc, że mamy tutaj program napisany, aby radzić sobie z błędnym kompilatorem w nietypowym środowisku: na przykład środowisko, w którym sizeof(float *) > sizeof(int)i kompilator nie odpowiedniej konwencji wywoływania wskaźników, chyba że rzucisz je w punkcie wezwania.

Nie znam takiego środowiska, ale to nie znaczy, że takiego nie było. Najbardziej prawdopodobnymi kandydatami, którzy przychodzą na myśl, są ograniczone kompilatory „malutkiego C” dla 8- i 16-bitowych mikrometrów we wczesnych latach 80. Nie zdziwiłbym się również, gdyby dowiedział się, że wczesni Crays mieli takie problemy.

zwol
źródło
1
Z pierwszą połową w pełni się zgadzam. A druga połowa to intrygujące i wiarygodne przypuszczenie.
chux - Przywróć Monikę
9

free przyjmuje tylko wskaźniki inne niż const jako parametr. Tak więc w przypadku wskaźników stałych wymagane jest jawne rzutowanie na wskaźnik inny niż stały.

Nie można zwolnić wskaźników stałych w C

Nikt
źródło