Warum werfen einige Leute in C den Zeiger, bevor sie ihn freigeben?

167

Ich arbeite an einer alten Codebasis und so ziemlich jeder Aufruf von free () verwendet eine Besetzung für sein Argument. Beispielsweise,

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

Dabei ist jeder Zeiger vom entsprechenden (und übereinstimmenden) Typ. Ich sehe überhaupt keinen Sinn darin. Es ist sehr alter Code, also frage ich mich, ob es eine K & R-Sache ist. Wenn ja, möchte ich tatsächlich die alten Compiler unterstützen, die dies möglicherweise erforderlich gemacht haben, daher möchte ich sie nicht entfernen.

Gibt es einen technischen Grund, diese Abgüsse zu verwenden? Ich sehe nicht einmal einen pragmatischen Grund, sie zu benutzen. Was bringt es, uns kurz vor dem Freigeben an den Datentyp zu erinnern?

EDIT: Diese Frage ist kein Duplikat der anderen Frage. Die andere Frage ist ein Sonderfall dieser Frage, der meiner Meinung nach offensichtlich ist, wenn die engen Wähler alle Antworten lesen würden.

Kolophon: Ich gebe der "konstanten Antwort" das Häkchen, weil dies ein guter Grund ist, warum dies möglicherweise getan werden muss. Die Antwort darauf, dass es sich um einen Pre-ANSI C-Brauch handelt (zumindest bei einigen Programmierern), scheint jedoch der Grund zu sein, warum er in meinem Fall verwendet wurde. Viele gute Punkte von vielen Leuten hier. Vielen Dank für Ihre Beiträge.

Dr. Person Person II
quelle
13
"Was bringt es, uns kurz vor der Freigabe an den Datentyp zu erinnern?" Vielleicht um zu wissen, wie viel Speicher freigegeben wird?
m0skit0
12
@Codor Der Compiler führt die Freigabe nicht durch, das Betriebssystem.
m0skit0
20
@ m0skit0 "Vielleicht um zu wissen, wie viel Speicher freigegeben wird?" Typ ist nicht erforderlich, um zu wissen, wie viel zu befreien ist. Cast nur aus diesem Grund ist schlechte Codierung.
user694733
9
@ m0skit0 Casting aus Gründen der Lesbarkeit ist immer eine schlechte Codierung, da Casting die Interpretation von Typen ändert und schwerwiegende Fehler verbergen kann. Wenn Lesbarkeit erforderlich ist, sind Kommentare besser.
user694733
66
In alten Zeiten, als Dinosaurier um die Erde gingen und Programmierbücher schrieben, gab es meines Erachtens kein void*C vor dem Standard, sondern nur char*. Wenn Ihre archäologischen Funde also Code enthüllen, der den Parameter in free () umwandelt, muss er meiner Meinung nach entweder aus dieser Zeit stammen oder von einer Kreatur aus dieser Zeit geschrieben worden sein. Ich kann jedoch keine Quelle dafür finden, daher werde ich nicht antworten.
Lundin

Antworten:

171

Casting kann erforderlich sein, um Compiler-Warnungen aufzulösen, wenn die Zeiger vorhanden sind const. Hier ist ein Beispiel für Code, der eine Warnung auslöst, ohne das Argument free freizugeben:

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

Und der Compiler (gcc 4.8.3) sagt:

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;

Wenn Sie free((float*) velocity);den Compiler verwenden, hört er auf, sich zu beschweren.

Manos Nikolaidis
quelle
2
@ m0skit0 das erklärt nicht, warum jemand float*vor dem Befreien gewirkt hat . Ich habe es free((void *)velocity);mit gcc 4.8.3 versucht . Natürlich würde es mit einem alten Compiler nicht funktionieren
Manos Nikolaidis
54
Aber warum sollten Sie konstant konstanten Speicher zuweisen müssen? Du könntest es niemals benutzen!
Nils_M
33
@Nils_M Es ist ein vereinfachtes Beispiel, um einen Punkt zu machen. Was ich im eigentlichen Code einer Funktion getan habe, ist, nicht konstanten Speicher zuzuweisen, Werte zuzuweisen, in einen konstanten Zeiger umzuwandeln und ihn zurückzugeben. Jetzt gibt es einen Zeiger auf den vorab zugewiesenen konstanten Speicher, den jemand freigeben muss.
Manos Nikolaidis
2
Beispiel : „Diese Unterprogramme geben die Zeichenfolge im neu zugewiesenen Speicher zurück, auf die * stringValueP zeigt und die Sie eventuell freigeben müssen. Manchmal wird die Betriebssystemfunktion, mit der Sie Speicher freigeben, so deklariert, dass sie einen Zeiger auf etwas Nichtkonstantes als Argument verwendet, da * stringValueP ein Zeiger auf eine Konstante ist. “
Carsten S
3
Fehlerhaft: Wenn eine Funktion const char *pals Argument verwendet und dann freigegeben wird, ist es nicht richtig p, char*vor dem Aufruf von free zu konvertieren. Es ist nicht zu deklarieren, dass es const char *pan erster Stelle steht, da es modifiziert *p und entsprechend deklariert werden sollte. (Und wenn es einen const-Zeiger anstelle eines Zeigers auf const braucht , int *const pmüssen Sie nicht umwandeln , da dies tatsächlich legal ist und daher ohne die
Ray
59

Pre-Standard C hatte nein, void*aber nur char*, also mussten Sie alle übergebenen Parameter umwandeln. Wenn Sie auf alten C-Code stoßen, finden Sie möglicherweise solche Casts.

Ähnliche Frage mit Referenzen .

Als der erste C-Standard veröffentlicht wurde, wechselten die Prototypen für malloc und free von den char*zu den void*heute noch vorhandenen.

Und natürlich sind solche Abgüsse in Standard C überflüssig und beeinträchtigen nur die Lesbarkeit.

Lundin
quelle
23
Aber warum sollten Sie das Argument freeauf den gleichen Typ umwandeln, der es bereits ist?
Jwodder
4
@chux Das Problem mit Pre-Standard ist nur das: Es gibt keine Verpflichtungen für irgendetwas. Die Leute zeigten nur auf das K & R-Buch für Canon, weil das das einzige war, was sie hatten. Und wie wir aus mehreren Beispielen in der 2. Ausgabe von K & R sehen können, ist K & R selbst verwirrt darüber, wie Casts des Parameters freein Standard C funktionieren (Sie müssen nicht umwandeln). Ich habe die 1. Ausgabe nicht gelesen, daher kann ich nicht sagen, ob sie auch in den 80er Jahren vor dem Standard verwirrt waren.
Lundin
7
Pre-Standard C hatte keine void*, aber es gab auch keine Funktionsprototypen, so dass das Casting des Arguments von freeselbst in K & R immer noch nicht erforderlich war (vorausgesetzt, alle Datenzeigertypen verwendeten dieselbe Darstellung).
Ian Abbott
6
Aus mehreren Gründen, die bereits in den Kommentaren erwähnt wurden, halte ich diese Antwort nicht für sinnvoll.
R .. GitHub STOP HELPING ICE
4
Ich sehe nicht ein, wie diese Antwort wirklich irgendetwas Relevantes beantworten würde. Die ursprüngliche Frage betrifft Abgüsse zu anderen Typen, nicht nur zu char *. Welchen Sinn würde es in alten Compilern ohne machen void? Was würden solche Casts erreichen?
AnT
34

Hier ist ein Beispiel, bei dem Free ohne Besetzung fehlschlagen würde:

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 :)

In C können Sie eine Warnung erhalten (eine in VS2012). In C ++ wird eine Fehlermeldung angezeigt.

Abgesehen von seltenen Fällen bläht das Casting nur den Code auf ...

Bearbeiten: Ich habe gegossen, um den Fehler void*nicht int*zu demonstrieren. Es funktioniert genauso, wie int*es void*implizit konvertiert wird . int*Code hinzugefügt .

egur
quelle
Beachten Sie, dass in dem in der Frage angegebenen Code die Besetzungen nicht zu void *, sondern zu float *und sind char *. Diese Darsteller sind nicht nur irrelevant, sie sind auch falsch.
Andrew Henle
1
Die Frage ist eigentlich das Gegenteil.
m0skit0
1
Ich verstehe die Antwort nicht. In welchem ​​Sinne würde free(p)scheitern? Würde es einen Compilerfehler geben?
Codor
1
Das sind gute Punkte. Gleiches gilt constnatürlich auch für Qualifikationszeiger.
Lundin
2
volatileexistiert seit C standardisiert wurde, wenn nicht länger. Es wurde nicht in C99 hinzugefügt.
R .. GitHub STOP HELPING ICE
30

Alter Grund: 1. Bei Verwendung von free((sometype*) ptr)Code wird explizit angegeben, welcher Typ der Zeiger als Teil des free()Aufrufs betrachtet werden soll. Die explizite Besetzung ist nützlich, wenn sie free()durch eine (Do-it-yourself) ersetzt wird DIY_free().

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

A DIY_free()war (ist) eine Möglichkeit, insbesondere im Debug-Modus, eine Laufzeitanalyse des freigegebenen Zeigers durchzuführen. Dies wird häufig mit a DIY_malloc()kombiniert, um Sententials, globale Speicherauslastungszahlen usw. hinzuzufügen. Meine Gruppe hat diese Technik jahrelang verwendet, bevor modernere Tools erscheinen. Es war verpflichtet, dass der Gegenstand, der frei war, auf den Typ gegossen wurde, der ursprünglich zugewiesen wurde.

  1. Angesichts der vielen Stunden, die für das Aufspüren von Speicherproblemen usw. aufgewendet wurden, würden kleine Tricks wie das Freischalten des Typs beim Suchen und Eingrenzen des Debuggens hilfreich sein.

Modern: Vermeiden constund volatileWarnen, wie von Manos Nikolaidis @ und @egur angesprochen . Dachte , ich würde die Auswirkungen der Anmerkung 3 - Qualifikation : const, volatile, undrestrict .

[bearbeiten] char * restrict *rp2Per @R .. Kommentar hinzugefügt

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 - Monica wieder einsetzen
quelle
3
restrictist nur aufgrund der Position rpkein Problem - es wirkt sich auf das Objekt aus, nicht auf den Typ, auf den verwiesen wird. Wenn Sie stattdessen hätten char *restrict *rp, wäre es wichtig.
R .. GitHub STOP HELPING ICE
16

Hier ist eine andere alternative Hypothese.

Uns wird gesagt, dass das Programm vor C89 geschrieben wurde, was bedeutet, dass es keine Art von Nichtübereinstimmung mit dem Prototyp von umgehen kann free, weil es nicht nur constweder void *vor noch vor C89 gab, noch gab es keine ein Funktionsprototyp vor C89. stdlib.hselbst war eine Erfindung des Komitees. Wenn die System-Header sich die Mühe gemacht hätten, überhaupt zu deklarieren free, hätten sie es so gemacht:

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

Der entscheidende Punkt hierbei ist, dass der Compiler aufgrund des Fehlens von Funktionsprototypen keine Überprüfung des Argumenttyps durchgeführt hat . Es wurden die Standardargument-Promotions angewendet (die gleichen, die immer noch für verschiedene Funktionsaufrufe gelten), und das war es. Die Verantwortung für die Argumentation auf jeder Call-Site, die den Erwartungen des Angerufenen entspricht, liegt vollständig beim Programmierer.

Dies bedeutet jedoch nicht, dass es notwendig war, das Argument freeauf die meisten K & R-Compiler zu übertragen. Eine Funktion wie

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

sollte korrekt kompiliert worden sein. Ich denke, wir haben hier ein Programm, das geschrieben wurde, um mit einem fehlerhaften Compiler für eine ungewöhnliche Umgebung fertig zu werden: zum Beispiel eine Umgebung, in sizeof(float *) > sizeof(int)der der Compiler dies nicht tun würde die entsprechende Aufrufkonvention für Zeiger verwenden wenn Sie sie nicht an der Stelle umwandeln des Anrufs.

Ich kenne keine solche Umgebung, aber das bedeutet nicht, dass es keine gab. Die wahrscheinlichsten Kandidaten, die mir in den Sinn kommen, sind reduzierte "winzige C" -Compiler für 8- und 16-Bit-Mikros in den frühen 1980er Jahren. Ich wäre auch nicht überrascht zu erfahren, dass frühe Crays solche Probleme hatten.

zwol
quelle
1
Der ersten Hälfte stimme ich voll und ganz zu. Und die 2. Hälfte ist eine faszinierende und plausible Vermutung.
chux