95 Stimmen

Ist es garantiert sicher, memcpy(0,0,0) auszuführen?

Ich kenne mich mit dem C-Standard nicht so gut aus, also bitte ich um Nachsicht mit mir.

Ich würde gerne wissen, ob die Norm garantiert, dass memcpy(0,0,0) sicher ist.

Die einzige Einschränkung, die ich finden konnte, ist, dass das Verhalten undefiniert ist, wenn sich die Speicherbereiche überschneiden...

Aber können wir davon ausgehen, dass sich die Speicherbereiche hier überschneiden?

8 Stimmen

Mathematisch gesehen ist die Schnittmenge von zwei leeren Mengen leer.

0 Stimmen

Ich wollte für Sie prüfen, was (x)libC für Sie tut, aber da es ist asm (elibc/glibc hier), es ist ein bisschen zu kompliziert für einen frühen Morgen :)

0 Stimmen

Warum würde Sie das tun? Übrigens sind überlappende Speicherbereiche nicht der einzige Grund für UB mit memcpy .

82voto

templatetypedef Punkte 343693

Ich besitze einen Entwurf der C-Norm (ISO/IEC 9899:1999), und darin stehen einige interessante Dinge über diesen Aufruf. Zunächst einmal heißt es (§7.21.1/2) in Bezug auf memcpy dass

Wenn ein Argument, das als size_t n gibt die Länge des Arrays für eine Funktion angibt, kann n bei einem Aufruf dieser Funktion den Wert Null haben. Wenn nicht ausdrücklich angegeben in der Beschreibung einer bestimmten Funktion in diesem Unterabschnitt nicht 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 lokalisiert, kein Vorkommen, eine Funktion, die zwei Zeichenfolgen vergleicht Zeichenfolgen vergleicht, gibt Null zurück, und eine Funktion, die Zeichen kopiert, kopiert Null Zeichen.

Der hier angegebene Hinweis weist darauf 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 ein Zeiger außerhalb des Adressraums des Programms, oder ein Null-Zeiger oder ein Zeiger auf einen nicht änderbaren Speicher, wenn der entsprechende Parameter nicht const-qualifiziert ist) oder ein Typ (nach Promotion), der nicht von einer Funktion erwartet wird mit variabler Anzahl von Argumenten erwartet wird, das Verhalten ist undefiniert .

Es sieht also so aus, dass nach der C-Spezifikation der Aufruf von

memcpy(0, 0, 0)

führt zu undefiniertem Verhalten, da Null-Zeiger als "ungültige Werte" betrachtet werden.

Allerdings wäre ich sehr erstaunt, wenn eine tatsächliche Umsetzung von memcpy Die meisten intuitiven Implementierungen, die mir einfallen, würden überhaupt nichts tun, wenn man sagt, dass man null Bytes kopieren soll.

1 Stimmen

+1 Ich habe diesen Absatz übersehen und bin direkt zu memcpy . Ich Dummerchen. Ich hätte es besser wissen müssen, als anzunehmen, dass Programmierer diese Art von Informationen für jede einzelne Funktionsbeschreibung wiederholen würden.

4 Stimmen

Ich kann bestätigen, dass die zitierten Teile aus dem Normentwurf im endgültigen Dokument identisch sind. Es sollte keine Probleme mit einem solchen Aufruf geben, aber es wäre immer noch ein undefiniertes Verhalten, auf das Sie sich verlassen. Die Antwort auf die Frage, ob es garantiert ist, lautet also "nein".

0 Stimmen

@DevSolar- Danke für die Bestätigung! Es wäre wirklich peinlich gewesen, wenn alles, was ich gesagt habe, völlig falsch gewesen wäre :-)

28voto

user1998586 Punkte 652

Nur zum Spaß, die Release-Notes für gcc-4.9 zeigen, dass der Optimierer diese Regeln nutzt und zum Beispiel die Bedingung in

int copy (int* dest, int* src, size_t nbytes) {
    memmove (dest, src, nbytes);
    if (src != NULL)
        return *src;
    return 0;
}

was dann zu unerwarteten Ergebnissen führt, wenn copy(0,0,0) aufgerufen wird (siehe https://gcc.gnu.org/gcc-4.9/porting_to.html ).

Ich bin etwas zwiegespalten, was das Verhalten von gcc-4.9 angeht; das Verhalten mag zwar standardkonform sein, aber die Möglichkeit, memmove(0,0,0) aufzurufen, ist manchmal eine nützliche Erweiterung dieser Standards.

2 Stimmen

Interessant. Ich verstehe Ihre Ambivalenz, aber dies ist der Kern der Optimierungen in C: der Compiler setzt voraus. dass Entwickler bestimmte Regeln befolgen und somit leitet ab. dass einige Optimierungen gültig sind (was sie sind, wenn die Regeln befolgt werden).

0 Stimmen

@MatthieuM.: Ja, aber das ist besonders dumm. Eine grobe Ecke wurde in die Spezifikation von memcpy die es in keiner mir bekannten Implementierung gibt, nicht um die Einheitlichkeit zu erhöhen, sondern um absichtlich von ihr abzuweichen.

0 Stimmen

@tmyklebu: Es wäre sinnvoll, auf einigen Plattformen zu implementieren memcpy als etwas wie char *end=src+count;while(src<end){*dest++=*src++;} [Erspart die Notwendigkeit einer Änderung count während der Schleife]. Die Übergabe eines ungültigen src Zeiger würde UB auch dann auslösen, wenn count ist Null.

1voto

VonC Punkte 1117238

Sie können auch diese Verwendung von memmove gesehen in Git 2.14.x (Q3 2017)

Ver Commit 168e635 (16. Juli 2017), und 1773664 übertragen , f331ab9 übertragen , festlegen 5783980 (15 Jul 2017) von René Scharfe ( rscharfe ) .
(Zusammengefasst von Junio C. Hamano -- gitster -- en 32f9025 übertragen , 11. August 2017)

Es verwendet eine Hilfsmakro MOVE_ARRAY die die Größe berechnet auf der Grundlage der angegebenen Anzahl von Elementen für uns und unterstützt NULL Zeiger, wenn diese Zahl Null ist.
Rohe memmove(3) Anrufe mit NULL kann den Compiler dazu veranlassen, später (übereifrig) zu optimieren NULL Kontrollen.

MOVE_ARRAY fügt ein sicheres und bequemes Hilfsmittel zum Verschieben von sich möglicherweise überschneidenden Bereichen 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 im Gegensatz zu memmove(3) es unterstützt NULL Zeiger, wenn 0 Elemente verschoben werden sollen.

#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 die Makro BUILD_ASSERT_OR_ZERO die eine Abhängigkeit zur Erstellungszeit behauptet, als Ausdruck (mit @cond ist die Bedingung zur Kompilierzeit, 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))

5 Stimmen

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 mit einer Implementierung möglich, die garantiert, dass memmove(any,any,0) ein no-op ist. Solange ein Compiler nicht in der Lage ist, einen Aufruf von memmove() durch einen Aufruf von memmoveAtLeastOneByte() zu ersetzen, führt die Umgehung der "Optimierung" von cleveren/dummen Compilern im Allgemeinen zu einem zusätzlichen Vergleich, den ein Compiler nicht vermeiden kann.

-2voto

Kai Petzke Punkte 1419

Nein, memcpy(0,0,0) ist nicht sicher. Die Standardbibliothek wird bei diesem Aufruf wahrscheinlich nicht versagen. In einer Testumgebung könnte jedoch etwas 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.

0 Stimmen

Ich kann nicht erkennen, dass Ihre Antwort etwas zu den bestehenden Antworten hinzufügt, die bereits darauf hingewiesen haben, dass dies nicht sicher war, mit Auszügen aus der Norm.

1 Stimmen

Ich habe eine Begründung geschrieben, warum bestimmte Implementierungen von memcpy ("in einer Testumgebung") zu Problemen mit memcpy(0, 0, 0) führen können. Ich denke, das ist neu.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X