Ich bin nicht so gut mit dem C-Standard vertraut, bitte nehmen Sie Kontakt mit mir auf.
Ich würde gerne wissen, ob es standardmäßig garantiert ist, dass memcpy(0,0,0)
es sicher ist.
Die einzige Einschränkung, die ich finden konnte, ist, dass das Verhalten undefiniert ist, wenn sich die Speicherbereiche überlappen ...
Aber können wir berücksichtigen, dass sich die Speicherbereiche hier überlappen?
c
memcpy
language-lawyer
null-pointer
Matthieu M.
quelle
quelle
memcpy(0,0,0)
Randfall ist, als auch weil ich denke, dass es eines der seltsamsten Teile des C-Codes ist, die ich gesehen habe.memcpy(outp, inp, len)
? Und dass dies in Code auftreten könnte, wooutp
undinp
dynamisch zugeordnet sind und anfänglich sind0
? Dies funktioniert zB mitp = realloc(p, len+n)
wannp
undlen
sind0
. Ich selbst habe einen solchenmemcpy
Aufruf verwendet - obwohl es sich technisch gesehen um UB handelt, bin ich noch nie auf eine Implementierung gestoßen, bei der es sich nicht um ein No-Op handelt und die ich nie erwartet habe.memcpy(0, 0, 0)
soll höchstwahrscheinlich einen dynamischen, nicht statischen Aufruf darstellen ... dh diese Parameterwerte müssen keine Literale sein.Antworten:
Ich habe eine Entwurfsversion der C-Norm (ISO / IEC 9899: 1999), und es gibt einige lustige Dinge zu diesem Aufruf zu sagen. Für den Anfang erwähnt sie (§7.21.1 / 2) in Bezug auf
memcpy
dasDie hier angegebene Referenz weist darauf hin:
Es sieht also so aus, als würde man gemäß der C-Spezifikation anrufen
memcpy(0, 0, 0)
führt zu undefiniertem Verhalten, da Nullzeiger als "ungültige Werte" betrachtet werden.
Trotzdem wäre ich äußerst erstaunt, wenn eine tatsächliche Implementierung von
memcpy
pleite wäre, wenn Sie dies tun würden, da die meisten intuitiven Implementierungen, die mir einfallen, überhaupt nichts bewirken würden, wenn Sie sagen würden, dass sie null Bytes kopieren sollen.quelle
realloc(0, 0)
. Die Anwendungsfälle sind ähnlich und ich habe beide verwendet (siehe meinen Kommentar unter der Frage). Es ist sinnlos und bedauerlich, dass der Standard diese UB macht.Nur zum Spaß zeigen die Versionshinweise für gcc-4.9 an, dass sein Optimierer diese Regeln verwendet und beispielsweise die Bedingung in entfernen kann
int copy (int* dest, int* src, size_t nbytes) { memmove (dest, src, nbytes); if (src != NULL) return *src; return 0; }
Dies führt dann zu unerwarteten Ergebnissen, wenn
copy(0,0,0)
es aufgerufen wird (siehe https://gcc.gnu.org/gcc-4.9/porting_to.html ).Ich bin etwas ambivalent in Bezug auf das Verhalten von gcc-4.9; Das Verhalten ist möglicherweise standardkonform, aber das Aufrufen von memmove (0,0,0) ist manchmal eine nützliche Erweiterung dieser Standards.
quelle
char *p = 0; int i=something;
, die Auswertung des Ausdrucks(p+i)
ergibt ein undefiniertes Verhalten, selbst wenni
es Null ist.memcpy()
es erlaubt sein sollte, eine Zeigerarithmetik für seine Argumente durchzuführen, bevor sichergestellt wird, dass eine Zählung ungleich Null vorliegt, ist eine andere Frage [wenn ich die Standards entwerfen würde, würde ich wahrscheinlich angeben, dass wennp
null ist, abgefangen werdenp+0
könnte, abermemcpy(p,p,0)
nichts tun würde]. Ein viel größeres Problem, IMHO, ist die Offenheit der meisten undefinierten Verhaltensweisen. Zwar gibt es einige Dinge , die wirklich sollten nicht definiertes Verhalten (zB Berufung darstellenfree(p)
...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 Ergebnis einer 0 oder a 1) oder sollte so angegeben werden, dass sich ein Verhalten ergibt, das etwas lockerer ist als das von der Implementierung definierte (Compiler sollten alle möglichen Konsequenzen eines z. B. ganzzahligen Überlaufs dokumentieren müssen, aber nicht angeben, welche Konsequenz in einem bestimmten Fall auftreten würde).Sie können diese Verwendung
memmove
auch in Git 2.14.x (Q3 2017) berücksichtigen.Siehe Commit 168e635 (16. Juli 2017) und Commit 1773664 , Commit f331ab9 , Commit 5783980 (15. Juli 2017) von René Scharfe (
rscharfe
) .(Zusammengeführt von Junio C Hamano -
gitster
- in Commit 32f9025 , 11. August 2017)Es verwendet ein Hilfsmakro,
MOVE_ARRAY
das die Größe basierend auf der für uns angegebenen Anzahl von Elementen berechnet undNULL
Zeiger unterstützt, wenn diese Anzahl Null ist.Rohe
memmove(3)
Aufrufe mitNULL
können dazu führen, dass der Compiler (zu eifrig) spätereNULL
Überprüfungen optimiert .#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \ BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src)))) static inline void move_array(void *dst, const void *src, size_t n, size_t size) { if (n) memmove(dst, src, st_mult(size, n)); }
Beispiele :
- memmove(dst, src, (n) * sizeof(*dst)); + MOVE_ARRAY(dst, src, n);
Es verwendet das Makro,
BUILD_ASSERT_OR_ZERO
das eine Abhängigkeit von@cond
der Erstellungszeit behauptet, als Ausdruck (wobei dies die Bedingung für die Kompilierungszeit ist, die wahr sein muss).Die Kompilierung schlägt fehl, wenn die Bedingung nicht erfüllt ist oder vom Compiler nicht ausgewertet werden kann.
#define BUILD_ASSERT_OR_ZERO(cond) \ (sizeof(char [1 - 2*!(cond)]) - 1)
Beispiel:
#define foo_to_char(foo) \ ((char *)(foo) \ + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
quelle
Nein,
memcpy(0,0,0)
ist nicht sicher. Die Standardbibliothek wird bei diesem Aufruf wahrscheinlich nicht fehlschlagen. In einer Testumgebung ist jedoch möglicherweise zusätzlicher Code in memcpy () vorhanden, um Pufferüberläufe und andere Probleme zu erkennen. Und wie diese spezielle Version von memcpy () auf NULL-Zeiger reagiert, ist nicht definiert.quelle