Wie funktioniert die interne Implementierung von memcpy?

Lesezeit: 4 Minuten

Benutzer-Avatar
PersonMitName

Wie funktioniert die Standard-C-Funktion ‘memcpy’? Es muss einen (großen) Teil des RAM in einen anderen Bereich im RAM kopieren. Da ich weiß, dass Sie in Assembly (mit der mov-Anweisung) nicht direkt von RAM zu RAM wechseln können, vermute ich, dass beim Kopieren ein CPU-Register als Zwischenspeicher verwendet wird?

Aber wie kopiert es? Nach Blöcken (wie würde es nach Blöcken kopieren?), nach einzelnen Bytes (char) oder dem größten Datentyp, den sie haben (kopieren in langen langen Doppeln – das sind 12 Bytes auf meinem System).

BEARBEITEN: Ok, anscheinend können Sie Daten direkt von RAM zu RAM verschiebenich bin kein Montageexperte und habe alles, was ich über die Montage gelernt habe, aus diesem Dokument (X86-Montageanleitung), in dem im Abschnitt über die mov-Anweisung erwähnt wird, dass Sie nicht von RAM zu RAM wechseln können. Anscheinend stimmt das nicht.

  • Dies ist plattformspezifisch. Bitte geben Sie eine Plattform an.

    – Oliver Charlesworth

    6. Juli 2013 um 2:30 Uhr

  • Ich verwende Linux, Mac und Windows (32-Bit, 64-Bit bzw. 32-Bit), aber ich habe diese Frage während der Verwendung von Linux gestellt.

    – PersonMitName

    6. Juli 2013 um 3:03 Uhr

Beruht. Im Allgemeinen können Sie in einem einzigen Zyklus nichts Größeres als das größte nutzbare Register physisch kopieren, aber so funktionieren Maschinen heutzutage nicht wirklich. In der Praxis interessiert Sie weniger, was die CPU tut, als vielmehr die Eigenschaften von DRAM. Die Speicherhierarchie der Maschine spielt eine entscheidende Rolle bei der Durchführung dieser Kopie auf die schnellstmögliche Weise (laden Sie zB ganze Cache-Zeilen? Wie groß ist eine DRAM-Zeile in Bezug auf die Kopieroperation?). Eine Implementierung könnte sich stattdessen dafür entscheiden, eine Art von Vektorbefehlen zu implementieren memcpy. Ohne Bezugnahme auf eine bestimmte Implementierung handelt es sich effektiv um eine Byte-für-Byte-Kopie mit einem einstelligen Puffer.

Hier ist ein lustiger Artikel das beschreibt das Abenteuer einer Person in die Optimierung memcpy. Der wichtigste Punkt zum Mitnehmen ist, dass es immer auf eine bestimmte Architektur und Umgebung ausgerichtet ist, basierend auf den Anweisungen, die Sie kostengünstig ausführen können.

  • Buf für einen bestimmten Fall, z. B. wenn i = 1, 2 oder 4 ist. was dann?

    – mohammadsdtmnd

    6. März um 14:06 Uhr


Die Implementierung von memcpy ist sehr spezifisch für das System, in dem es implementiert ist. Implementierungen sind oft hardwareunterstützt.

Memory-to-Memory-Mov-Anweisungen sind nicht so ungewöhnlich – sie gibt es mindestens seither PDP-11 Zeiten, in denen man so etwas schreiben könnte:

    MOV FROM, R2
    MOV TO,   R3
    MOV R2,   R4
    ADD LEN,  R4
CP: MOV (R2+), (R3+) ; "(Rx+)" means "*Rx++" in C
    CMP R2, R4
    BNE CP

Die kommentierte Zeile entspricht in etwa C’s

*to++ = *from++;

Zeitgenössische CPUs haben Anweisungen, die implementieren memcpy direkt: Sie laden spezielle Register mit den Quell- und Zieladressen, rufen einen Speicherkopierbefehl auf und lassen die CPU den Rest erledigen.

  • “sie gibt es seit mindestens PDP-11-Zeiten” – viel länger.

    – Jim Balter

    6. Juli 2013 um 7:47 Uhr

  • @JimBalter Das überrascht mich überhaupt nicht 🙂

    – Sergej Kalinitschenko

    6. Juli 2013 um 9:51 Uhr

  • to und from sind void-Zeiger, und ich dachte, Sie können void-Zeiger nicht dereferenzieren. Würden Sie sie zuerst eingeben, um sie umzuwandeln? (unsigned char*)

    – Rockstar5645

    12. September 2019 um 2:30 Uhr

  • @ Rockstar5645 Assembly hat kein Typkonzept, daher dereferenziert es gerne jede Adresse, die Sie als übergeben void*. Wenn Sie eine Implementierung in C schreiben, müssen Sie diese Zeiger natürlich auf etwas umwandeln, das Sie dereferenzieren können, wie z unsigned char*.

    – Sergej Kalinitschenko

    12. September 2019 um 2:41 Uhr

Benutzer-Avatar
ouah

Eine triviale Implementierung von memcpy ist:

 while (n--) *s2++ = *s1++;

Aber glibc verwendet normalerweise einige clevere Implementierungen im Assembler-Code. memcpy Aufrufe sind normalerweise inline.

Auf x86 prüft der Code, ob der Größenparameter ein wörtliches Vielfaches von ist 2 oder ein Vielfaches von 4 (unter Verwendung gcc builtins-Funktionen) und verwendet eine Schleife mit movl Anleitung (Kopie 4 Bytes), andernfalls ruft es den allgemeinen Fall auf.

Der allgemeine Fall verwendet die schnelle Blockkopie-Assemblierung unter Verwendung rep und movsl Anweisungen.

  • aber s2 und s1 sind void-Zeiger, und ich dachte, Sie könnten void-Zeiger nicht dereferenzieren.

    – Rockstar5645

    12. September 2019 um 2:29 Uhr

  • @ouah – warum verwenden movl nur auf Größen, die ein Vielfaches von 4 sind und nicht immer versuchen, zu verwenden movl? Wenn Sie insgesamt 50 Bytes kopieren müssen, können Sie nicht mit 12 kopieren movl und 2 mov?

    – joepol

    13. Mai 2021 um 14:27 Uhr

  • @ Rockstar5645 – du musst vorher casten, ich glaube, ouah hat darauf verwiesen: gcc memcpy-Implementierung

    – joepol

    13. Mai 2021 um 14:35 Uhr


1371520cookie-checkWie funktioniert die interne Implementierung von memcpy?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy