Ist es garantiert sicher, memcpy(0,0,0) auszuführen?
Lesezeit: 8 Minuten
Matthias M.
Ich kenne mich mit dem C-Standard nicht so gut aus, also bitte haben Sie Geduld mit mir.
Ich würde gerne wissen, ob dies durch den Standard garantiert ist memcpy(0,0,0) ist sicher.
Die einzige Einschränkung, die ich finden konnte, ist, dass das Verhalten undefiniert ist, wenn sich die Speicherbereiche überlappen …
Aber können wir davon ausgehen, dass sich die Speicherregionen hier überschneiden?
Mathematisch ist die Schnittmenge zweier leerer Mengen leer.
– Benoît
9. März 2011 um 8:22 Uhr
Ich wollte für Sie nachsehen, ob (x)libC das für Sie erledigt, aber wie es ist asm (hier elibc/glibc), ist für einen frühen Morgen etwas zu kompliziert 🙂
– Kevin
9. März 2011 um 8:25 Uhr
+1 Ich liebe diese Frage, weil es so ein seltsamer Grenzfall ist und weil ich denke memcpy(0,0,0) ist eines der seltsamsten Teile von C-Code, die ich je gesehen habe.
– Vorlagentypdef
9. März 2011 um 8:29 Uhr
@eq Willst du es wirklich wissen oder implizieren Sie, dass es keine Situationen gibt, in denen Sie es wollen würden? Haben Sie darüber nachgedacht, dass der eigentliche Anruf z. memcpy(outp, inp, len)? Und dass dies im Code auftreten könnte, wo outp und inp werden dynamisch zugewiesen und sind initial 0? Das funktioniert zB mit p = realloc(p, len+n) Wenn p und len sind 0. Ich selbst habe so einen benutzt memcpy call — obwohl es technisch gesehen UB ist, bin ich noch nie auf eine Implementierung gestoßen, bei der es kein No-Op ist und erwarte es auch nie.
– Jim Balter
9. März 2011 um 8:50 Uhr
@templatetypedef memcpy(0, 0, 0) soll höchstwahrscheinlich einen dynamischen, nicht statischen Aufruf darstellen … dh diese Parameterwerte müssen keine Literale sein.
– Jim Balter
9. März 2011 um 8:53 Uhr
Ich habe eine Entwurfsversion des C-Standards (ISO/IEC 9899:1999), und es gibt einige lustige Dinge über diesen Aufruf zu sagen. Für den Anfang erwähnt es (§7.21.1/2) in Bezug auf memcpy das
Wo ein Argument als deklariert wird size_t n gibt die Länge des Arrays für eine Funktion an, n kann bei einem Aufruf dieser Funktion den Wert Null annehmen. Sofern in der Beschreibung einer bestimmten Funktion in diesem Unterabschnitt nicht ausdrücklich anders angegeben, Zeigerargumente bei einem solchen Aufruf müssen weiterhin gültige Werte haben, wie in 7.1.4 beschrieben. Bei einem solchen Aufruf findet eine Funktion, die ein Zeichen findet, kein Vorkommen, eine Funktion, die zwei Zeichenfolgen vergleicht, gibt Null zurück, und eine Funktion, die Zeichen kopiert, kopiert null Zeichen.
Darauf weist die hier angegebene Referenz hin:
Wenn ein Argument für eine Funktion einen ungültigen Wert hat (z. B. einen Wert außerhalb der Domäne der Funktion oder einen Zeiger außerhalb des Adressraums des Programms, oder ein Nullzeigeroder ein Zeiger auf nicht modifizierbaren Speicher, wenn der entsprechende Parameter nicht konstant qualifiziert ist) oder ein Typ (nach der Heraufstufung), der von einer Funktion mit variabler Anzahl von Argumenten nicht erwartet wird, Das Verhalten ist undefiniert.
So sieht es laut C-Spec aus, Calling
memcpy(0, 0, 0)
führt zu undefiniertem Verhalten, da Nullzeiger als “ungültige Werte” betrachtet werden.
Das heißt, ich wäre absolut erstaunt, wenn eine tatsächliche Umsetzung von memcpy brach, wenn Sie dies taten, da die meisten intuitiven Implementierungen, die mir einfallen, überhaupt nichts bewirken würden, wenn Sie sagten, null Bytes zu kopieren.
Ich kann versichern, dass die zitierten Teile aus dem Norm-Entwurf im endgültigen Dokument identisch sind. Bei einem solchen Aufruf sollte es keine Probleme geben, aber es wäre immer noch ein undefiniertes Verhalten, auf das Sie sich verlassen. Die Antwort auf “ist es garantiert” ist also “nein”.
– DevSolar
9. März 2011 um 8:33 Uhr
Keine Implementierung, die Sie jemals in der Produktion verwenden werden, wird etwas anderes als eine No-Op für einen solchen Aufruf erzeugen, aber Implementierungen, die etwas anderes tun, sind zulässig und sinnvoll … z. B. ein C-Interpreter oder erweiterter Compiler mit Fehlerprüfung, der die ablehnt Rufen Sie an, weil es nicht konform ist. Das wäre natürlich nicht sinnvoll, wenn der Standard den Aufruf zulassen würde, wie es für realloc(0, 0). Die Anwendungsfälle sind ähnlich, und ich habe sie beide verwendet (siehe meinen Kommentar unter der Frage). Es ist sinnlos und schade, dass der Standard dieses UB macht.
– Jim Balter
9. März 2011 um 9:01 Uhr
“Ich wäre absolut erstaunt, wenn eine tatsächliche Implementierung von Memcpy kaputt gehen würde, wenn Sie dies tun würden” – Ich habe eine verwendet, die dies tun würde; Wenn Sie die Länge 0 mit gültigen Zeigern übergeben, wurden tatsächlich 65536 Bytes kopiert. (Seine Schleife dekrementierte die Länge und testete dann).
– MM
12. Juli 2014 um 6:01 Uhr
@MattMcNabb Diese Implementierung ist kaputt.
– Jim Balter
13. Juli 2014 um 22:16 Uhr
@MattMcNabb: Vielleicht “richtig” zu “tatsächlich” hinzufügen. Ich denke, wir alle haben nicht so gute Erinnerungen an alte Ghetto-C-Bibliotheken, und ich bin mir nicht sicher, wie viele von uns es schätzen, dass diese Erinnerungen zurückgerufen werden. 🙂
– tmyklebu
9. September 2014 um 11:20 Uhr
Benutzer1998586
Nur zum Spaß weisen die Versionshinweise für gcc-4.9 darauf hin, dass sein Optimierer diese Regeln verwendet und zum Beispiel die Bedingung in entfernen kann
Ich bin etwas ambivalent bezüglich des Verhaltens von gcc-4.9; Das Verhalten ist möglicherweise standardkonform, aber die Möglichkeit, memmove(0,0,0) aufzurufen, ist manchmal eine nützliche Erweiterung dieser Standards.
Interessant. Ich verstehe Ihre Ambivalenz, aber das ist das Herzstück der Optimierungen in C: der Compiler geht davon aus dass Entwickler bestimmten Regeln folgen und somit leitet ab dass einige Optimierungen gültig sind (was sie sind, wenn die Regeln befolgt werden).
– Matthias M.
19. Juli 2014 um 12:45 Uhr
@tmyklebu: Gegeben char *p = 0; int i=something;Auswertung des Ausdrucks (p+i) führt zu undefiniertem Verhalten, selbst wenn i ist Null.
– Superkatze
6. Dezember 2014 um 21:32 Uhr
@tmyklebu: Alle Zeigerarithmetik (außer Vergleichen) auf einer Nullzeigerfalle zu haben, wäre meiner Meinung nach eine gute Sache; ob memcpy() sollte es erlaubt sein, jede Zeigerarithmetik an seinen Argumenten durchzuführen, bevor sichergestellt wird, dass eine Zählung ungleich Null ist, ist eine andere Frage [if I were designing the standards, I would probably specify that if p is null, p+0 could trap, but memcpy(p,p,0) would do nothing]. Ein viel größeres Problem, IMHO, ist die Offenheit der meisten undefinierten Verhaltensweisen. Während es einige Dinge gibt, die wirklich sollte stellen undefiniertes Verhalten dar (z. B. Aufrufen free(p)…
– Superkatze
10. Dezember 2014 um 16:43 Uhr
…und anschließend performen p[0]=1;) Es gibt viele Dinge, die als unbestimmtes Ergebnis angegeben werden sollten (z. B. sollte ein relationaler Vergleich zwischen nicht verwandten Zeigern nicht als konsistent mit einem anderen Vergleich angegeben werden, sondern als entweder 0 oder 1), oder sollte so spezifiziert werden, dass er ein Verhalten ergibt, das etwas lockerer als durch die Implementierung definiert ist (Compiler sollten aufgefordert werden, alle möglichen Konsequenzen eines zB Integer-Überlaufs zu dokumentieren, aber nicht angeben, welche Konsequenzen in einem bestimmten Fall auftreten würden).
– Superkatze
10. Dezember 2014 um 16:52 Uhr
Bitte sagt mir jemand, warum bekomme ich kein Stackoverflow-Abzeichen “started a flame war” 🙂
– Benutzer1998586
20. Juni 2015 um 8:28 Uhr
Sie können auch diese Verwendung von in Betracht ziehen memmove gesehen in Git 2.14.x (Q3 2017)
Es verwendet ein Hilfsmakro MOVE_ARRAY die die Größe anhand der angegebenen Anzahl von Elementen für uns berechnet und stützt NULL
Zeiger, wenn diese Zahl Null ist.
Roh memmove(3) telefoniert mit NULL kann dazu führen, dass der Compiler später (übereifrig) optimiert NULL Schecks.
MOVE_ARRAY fügt einen sicheren und bequemen Helfer zum Verschieben potenziell überlappender Bereiche von Array-Einträgen hinzu.
Es leitet die Elementgröße ab, multipliziert automatisch und sicher, um die Größe in Bytes zu erhalten, führt eine grundlegende Typsicherheitsprüfung durch, indem es Elementgrößen vergleicht und unterscheidet memmove(3) es unterstützt NULL Zeiger wenn 0 Elemente verschoben werden sollen.
Es verwendet die Makro BUILD_ASSERT_OR_ZERO die eine Build-Time-Abhängigkeit geltend macht, als Ausdruck (with @cond die Bedingung zur Kompilierzeit ist, die wahr sein muss).
Die Kompilierung schlägt fehl, wenn die Bedingung nicht wahr ist oder vom Compiler nicht ausgewertet werden kann.
Die Existenz von Optimierern, die denken, dass „clever“ und „dumm“ Antonyme sind, macht den Test für n notwendig, aber effizienterer Code wäre im Allgemeinen bei einer Implementierung möglich, die garantiert, dass memmove(any,any,0) keine Operation wäre . Wenn ein Compiler einen Aufruf von memmove() nicht durch einen Aufruf von memmoveAtLeastOneByte() ersetzen kann, führt die Problemumgehung zum Schutz vor der “Optimierung” cleverer/dummer Compiler im Allgemeinen zu einem zusätzlichen Vergleich, den ein Compiler nicht eliminieren kann.
– Superkatze
14. August 2017 um 23:34 Uhr
Nein, memcpy(0,0,0) ist nicht sicher. Die Standardbibliothek wird bei diesem Aufruf wahrscheinlich nicht fehlschlagen. In einer Testumgebung kann jedoch zusätzlicher Code in memcpy() vorhanden sein, um Pufferüberläufe und andere Probleme zu erkennen. Und wie diese spezielle Version von memcpy() auf NULL-Zeiger reagiert, ist, nun ja, undefiniert.
14194200cookie-checkIst es garantiert sicher, memcpy(0,0,0) auszuführen?yes
Mathematisch ist die Schnittmenge zweier leerer Mengen leer.
– Benoît
9. März 2011 um 8:22 Uhr
Ich wollte für Sie nachsehen, ob (x)libC das für Sie erledigt, aber wie es ist asm (hier elibc/glibc), ist für einen frühen Morgen etwas zu kompliziert 🙂
– Kevin
9. März 2011 um 8:25 Uhr
+1 Ich liebe diese Frage, weil es so ein seltsamer Grenzfall ist und weil ich denke
memcpy(0,0,0)
ist eines der seltsamsten Teile von C-Code, die ich je gesehen habe.– Vorlagentypdef
9. März 2011 um 8:29 Uhr
@eq Willst du es wirklich wissen oder implizieren Sie, dass es keine Situationen gibt, in denen Sie es wollen würden? Haben Sie darüber nachgedacht, dass der eigentliche Anruf z.
memcpy(outp, inp, len)
? Und dass dies im Code auftreten könnte, wooutp
undinp
werden dynamisch zugewiesen und sind initial0
? Das funktioniert zB mitp = realloc(p, len+n)
Wennp
undlen
sind0
. Ich selbst habe so einen benutztmemcpy
call — obwohl es technisch gesehen UB ist, bin ich noch nie auf eine Implementierung gestoßen, bei der es kein No-Op ist und erwarte es auch nie.– Jim Balter
9. März 2011 um 8:50 Uhr
@templatetypedef
memcpy(0, 0, 0)
soll höchstwahrscheinlich einen dynamischen, nicht statischen Aufruf darstellen … dh diese Parameterwerte müssen keine Literale sein.– Jim Balter
9. März 2011 um 8:53 Uhr