Was ist besonders gefährlich daran, das Ergebnis von Malloc zu gießen?

86

Bevor die Leute anfangen, dies als Dup zu markieren, habe ich Folgendes gelesen, von denen keines die Antwort liefert, nach der ich suche:

  1. C FAQ: Was ist falsch daran, den Rückgabewert von malloc zu geben?
  2. SO: Sollte ich den Rückgabewert von malloc () explizit umwandeln?
  3. SO: Unnötige Zeigerabdrücke in C.
  4. SO: Werfe ich das Ergebnis von Malloc?

Sowohl die C-FAQ als auch viele Antworten auf die obigen Fragen führen einen mysteriösen Fehler an, mallocden der Rückgabewert des Castings verbergen kann. Keiner von ihnen gibt jedoch ein konkretes Beispiel für einen solchen Fehler in der Praxis. Achten Sie jetzt darauf, dass ich Fehler sagte , nicht Warnung .

Geben Sie nun den folgenden Code ein:

#include <string.h>
#include <stdio.h>
// #include <stdlib.h>

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

Das Kompilieren des obigen Codes mit gcc 4.2 mit und ohne Umwandlung gibt dieselben Warnungen aus, und das Programm wird ordnungsgemäß ausgeführt und liefert in beiden Fällen dieselben Ergebnisse.

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function malloc
anon@anon:~/$ ./nostdlib_malloc 
hello

Kann jemand ein bestimmtes Codebeispiel für einen Kompilierungs- oder Laufzeitfehler geben, der aufgrund des mallocRückgabewerts des Castings auftreten kann, oder ist dies nur eine urbane Legende?

Bearbeiten Ich bin auf zwei gut geschriebene Argumente zu diesem Thema gestoßen:

  1. Für das Casting: CERT-Hinweis: Das Ergebnis eines Funktionsaufrufs für die Speicherzuweisung wird sofort in einen Zeiger auf den zugewiesenen Typ umgewandelt
  2. Gegen Casting (404-Fehler vom 14.02.2012: Verwenden Sie die Internet Archive Wayback Machine- Kopie vom 27.01.2010. {18.03.2016: "Seite kann aufgrund von robots.txt nicht gecrawlt oder angezeigt werden."})
Robert S. Barnes
quelle
6
Durch das Casting von voidZeigern kann der Code als C ++ kompiliert werden. Einige Leute sagen, dass dies eine Funktion ist, ich würde sagen, dass es ein Fehler ist;)
Christoph
1
Lesen Sie auch die Kommentare zum ersten Ihrer Links, da darin beschrieben wird, was Sie tun sollten, anstatt zu gießen
Christoph
3
Ich werde CERTs Rat für die Aufnahme der Besetzung befolgen. Außerdem werde ich nie vergessen, stdlib.h einzuschließen. :)
Abhinav
1
Hier ist ein SO-Beispiel für einen Kompilierungslaufzeitfehler aufgrund des mallocRückgabewerts des Castings : Casting in einen int*64-Bit-Bogen.
John_West
1
Diese Frage ist Cnicht markiert C++(es handelt sich um zwei verschiedene Sprachen). Daher ist jede Diskussion (wie in einigen Antworten) für diese Frage nicht relevant.
user3629249

Antworten:

66

Sie erhalten keinen Compilerfehler , sondern eine Compilerwarnung . Wie die von Ihnen zitierten Quellen sagen (insbesondere die erste ), können Sie einen unvorhersehbaren Laufzeitfehler erhalten, wenn Sie die Besetzung ohne Einbeziehung verwendenstdlib.h .

Der Fehler auf Ihrer Seite ist also nicht die Besetzung, sondern das Vergessen, sie einzuschließen stdlib.h. Compiler können davon ausgehen, dass malloces sich um eine zurückgegebene Funktion inthandelt. Daher wird der void*tatsächlich zurückgegebene Zeiger aufgrund der expliziten Umwandlung mallocin intund dann in Ihren Zeigertyp konvertiert . Auf einigen Plattformen können intZeiger eine unterschiedliche Anzahl von Bytes belegen, sodass die Typkonvertierungen zu einer Beschädigung der Daten führen können.

Glücklicherweise geben moderne Compiler Warnungen aus, die auf Ihren tatsächlichen Fehler hinweisen. Siehe die von gccIhnen angegebene Ausgabe: Sie werden gewarnt, dass die implizite Deklaration ( int malloc(int)) nicht mit der integrierten deklariert ist malloc. So gccscheint es mallocauch ohne zu wissen stdlib.h.

Das Auslassen der Besetzung, um diesen Fehler zu vermeiden, ist meistens die gleiche Begründung wie das Schreiben

if (0 == my_var)

anstatt

if (my_var == 0)

da letzteres zu einem schwerwiegenden Fehler führen könnte, wenn man verwirrt =und ==, während der erste zu einem Kompilierungsfehler führen würde. Ich persönlich bevorzuge den letzteren Stil, da er meine Absicht besser widerspiegelt und ich diesen Fehler nicht mache.

Gleiches gilt für das Umwandeln des von zurückgegebenen Werts malloc: Ich bevorzuge es, explizit zu programmieren, und überprüfe im Allgemeinen, ob die Header-Dateien für alle von mir verwendeten Funktionen enthalten sind.

Ferdinand Beyer
quelle
2
Da der Compiler vor der inkompatiblen impliziten Deklaration warnt, ist dies anscheinend kein Problem, solange Sie auf Ihre Compiler-Warnungen achten.
Robert S. Barnes
4
@ Robert: Ja, unter bestimmten Voraussetzungen über den Compiler. Wenn Leute Ratschläge geben, wie man C im Allgemeinen am besten schreibt , können sie nicht davon ausgehen, dass die Person, die den Rat erhält, eine aktuelle Version von gcc verwendet.
Steve Jessop
4
Oh, und die Antwort auf die zweite Frage ist, dass der Aufrufer den Code enthält, um den Rückgabewert (den er für ein int hält) aufzunehmen und in T * umzuwandeln. Der Angerufene schreibt nur den Rückgabewert (als ungültig *) und gibt zurück. Abhängig von der aufrufenden Konvention: int return und void * return können sich an derselben Stelle befinden (Register oder Stack Slot) oder nicht; int und void * können gleich groß sein oder nicht; Das Konvertieren zwischen den beiden kann ein No-Op sein oder auch nicht. Es könnte also "einfach funktionieren" oder der Wert könnte beschädigt sein (einige Bits gehen möglicherweise verloren), oder der Anrufer könnte den völlig falschen Wert vollständig erfassen.
Steve Jessop
1
@ RobertS.Barnes zu spät zur Party, aber: Der Rückgabewert ist im Allgemeinen nicht Teil der Funktionssignatur, auch nicht in C ++. Der Linker generiert nur einen Sprung zu einem Symbol, das ist alles.
Peter - Stellen Sie Monica
3
Bei Verwendung der Besetzung ohne stdlib.h kann ein unvorhersehbarer Laufzeitfehler auftreten . Das ist wahr, aber das Nichteinschließen stdlib.hist bereits ein Fehler für sich, selbst wenn Sie nur Warnungen "implizite Deklaration" erhalten.
Jabberwocky
45

Eines der guten Argumente auf höherer Ebene gegen das Casting des Ergebnisses von mallocwird oft nicht erwähnt, obwohl es meiner Meinung nach wichtiger ist als die bekannten Probleme auf niedrigerer Ebene (wie das Abschneiden des Zeigers, wenn die Deklaration fehlt).

Eine gute Programmierpraxis besteht darin, Code zu schreiben, der so typunabhängig wie möglich ist. Dies bedeutet insbesondere, dass Typnamen im Code so wenig wie möglich oder am besten gar nicht erwähnt werden sollten. Dies gilt für Casts (vermeiden Sie unnötige Casts), Typen als Argumente von sizeof(vermeiden Sie die Verwendung von Typnamen in sizeof) und im Allgemeinen alle anderen Verweise auf Typnamen.

Typnamen gehören in Deklarationen. Typnamen sollten so weit wie möglich auf Deklarationen und nur auf Deklarationen beschränkt sein.

Unter diesem Gesichtspunkt ist dieses Codebit schlecht

int *p;
...
p = (int*) malloc(n * sizeof(int));

und das ist viel besser

int *p;
...
p = malloc(n * sizeof *p);

nicht einfach, weil es "das Ergebnis von nicht mallocumwandelt", sondern weil es typunabhängig ist (oder typunabhängig, wenn Sie es vorziehen), weil es sich automatisch an den Typ anpasst p, mit dem deklariert wird, ohne dass ein Eingreifen von erforderlich ist der Nutzer.

Ameise
quelle
Fwiw, ich denke, dies ist mehr oder weniger der gleiche Grund wie dieser: stackoverflow.com/questions/953112/…, aber ich konzentrierte mich eher auf die Typunabhängigkeit als auf das Heimwerken. Natürlich folgt der erste aus dem zweiten (oder umgekehrt), so dass es zumindest manchmal erwähnt wird . :)
Entspannen Sie sich
5
@unwind du meinst höchstwahrscheinlich eher DRY als DIY
kratenko
18

Es wird angenommen, dass nicht prototypisierte Funktionen zurückkehren int.

Sie werfen also einen intauf einen Zeiger. Wenn Zeiger intauf Ihrer Plattform breiter als s sind, ist dies ein sehr riskantes Verhalten.

Außerdem betrachten manche Leute Warnungen als Fehler, dh Code sollte ohne sie kompiliert werden.

Persönlich denke ich, dass die Tatsache, dass Sie nicht in void *einen anderen Zeigertyp umwandeln müssen, eine Funktion in C ist, und betrachten Sie Code, der beschädigt werden muss.

entspannen
quelle
14
Ich bin der Überzeugung, dass der Compiler mehr über die Sprache weiß als ich. Wenn er mich also vor etwas warnt, achte ich darauf.
György Andrasek
3
In vielen Projekten wird C-Code als C ++ kompiliert, wo Sie das umwandeln müssen void*.
Laalto
nit: " Standardmäßig wird davon ausgegangen, dass nicht prototypisierte Funktionen zurückkehren int." - Meinst du, es ist möglich, den Rückgabetyp von nicht prototypisierten Funktionen zu ändern?
PMG
1
@laalto - Es ist, aber es sollte nicht sein. C ist C, nicht C ++ und sollte mit einem C-Compiler kompiliert werden, nicht mit einem C ++ - Compiler. Es gibt keine Entschuldigung: GCC (einer der besten C-Compiler auf dem Markt) läuft auf nahezu jeder erdenklichen Plattform (und generiert auch hochoptimierten Code). Welche Gründe könnten Sie möglicherweise haben, um C mit einem C ++ - Compiler zu kompilieren, außer Faulheit und losen Standards?
Chris Lutz
3
Beispiel für Code, den Sie möglicherweise sowohl als C als auch als C ++ kompilieren möchten : #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. Warum Sie malloc in einer statischen Inline-Funktion aufrufen möchten, weiß ich wirklich nicht, aber Header, die in beiden funktionieren, sind kaum unbekannt.
Steve Jessop
11

Wenn Sie dies beim Kompilieren im 64-Bit-Modus tun, wird Ihr zurückgegebener Zeiger auf 32 Bit abgeschnitten.

EDIT: Tut mir leid, dass ich zu kurz bin. Hier ist ein Beispielcodefragment für Diskussionszwecke.

Main()
{
   char * c = (char *) malloc (2);
   printf ("% p", c);
}}

Angenommen, der zurückgegebene Heap-Zeiger ist größer als das, was in einem int dargestellt werden kann, z. B. 0xAB00000000.

Wenn malloc keinen Prototyp für die Rückgabe eines Zeigers hat, befindet sich der zurückgegebene int-Wert zunächst in einem Register, in dem alle signifikanten Bits gesetzt sind. Jetzt sagt der Compiler: "Okay, wie konvertiere ich und int in einen Zeiger". Dies wird entweder eine Vorzeichenerweiterung oder eine Nullerweiterung der 32-Bit niedriger Ordnung sein, von der Malloc mitgeteilt wurde, dass sie durch Weglassen des Prototyps "zurückkehrt". Da int signiert ist, denke ich, dass die Konvertierung eine Vorzeichenerweiterung ist, die in diesem Fall den Wert in Null konvertiert. Mit einem Rückgabewert von 0xABF0000000 erhalten Sie einen Zeiger ungleich Null, der auch Spaß macht, wenn Sie versuchen, ihn zu dereferenzieren.

Peeter Joot
quelle
1
Können Sie im Detail erklären, wie dies geschehen würde?
Robert S. Barnes
5
Ich denke, Peeter Joot hat herausgefunden, dass "Standardmäßig wird angenommen, dass nicht prototypisierte Funktionen int" ohne stdlib.h zurückgeben und sizeof (int) 32 Bit beträgt, während sizeof (ptr) 64 ist.
Test
4

Eine wiederverwendbare Softwareregel:

Wenn Sie eine Inline-Funktion schreiben, in der malloc () verwendet wird, führen Sie ein explizites Typ-Casting durch (z. B. (char *)), um es auch für C ++ - Code wiederverwendbar zu machen. Andernfalls wird sich der Compiler beschweren.

Prüfung
quelle
Mit der (kürzlich erfolgten) Aufnahme von Link-Time-Optimierungen in gcc (siehe gcc.gnu.org/ml/gcc/2009-10/msg00060.html ) ist es hoffentlich nicht mehr erforderlich, Inline-Funktionen in Header-Dateien zu deklarieren
Christoph
Du hast schlechte Ideen. Ist Ihnen bewusst, was portabel und plattformübergreifend zwischen verschiedenen Compilern / Versionen / Architekturen ist? ok, du darfst nicht. Was bedeutet dann wiederverwendbar?
Test
2
Beim Schreiben von C ++ ist malloc / free NICHT die richtige Methode. Verwenden Sie lieber new / delete. IE sollte es keine / nada / null Aufrufe zu malloc / free im C ++
user3629249
3
@ user3629249: Wenn Sie eine Funktion schreiben, die entweder aus C-Code oder C ++ - Code heraus verwendet werden muss, ist die Verwendung von malloc/ freefür beide möglicherweise besser als die Verwendung mallocin C und newC ++, insbesondere wenn Datenstrukturen zwischen C und C ++ gemeinsam genutzt werden Code und es besteht die Möglichkeit, dass ein Objekt in C-Code erstellt und in C ++ - Code freigegeben wird oder umgekehrt.
Supercat
3

Ein ungültiger Zeiger in C kann jedem Zeiger ohne explizite Umwandlung zugewiesen werden. Der Compiler gibt eine Warnung aus, kann jedoch in C ++ durch Typumwandlung malloc()in den entsprechenden Typ wiederverwendet werden . Ohne Typguss kann es auch in C verwendet werden , da C keine strenge Typprüfung ist . Da C ++ jedoch eine reine Typprüfung ist , ist es erforderlich, Cast malloc()in C ++ einzugeben.

Yogeesh HT
quelle
Wenn Sie malloc in C ++ verwenden, haben Sie besser einen verdammt guten Grund! ; p
antred