Hat dieser Aufruf von accept_init ein undefiniertes Verhalten ausgelöst?
Ja. "Nicht initialisiert" ist nur ein weiterer Wert, den ein Byte in der Rust Abstract Machine neben den üblichen 0x00 - 0xFF haben kann. Schreiben wir dieses spezielle Byte als 0xUU. (Weitere Informationen zu diesem Thema finden Sie in diesem Blog-Beitrag .) 0xUU wird von Kopien beibehalten, genau wie jeder andere mögliche Wert, den ein Byte haben kann, von Kopien beibehalten wird.
Die Details sind jedoch etwas komplizierter. Es gibt zwei Möglichkeiten, Daten in Rust im Speicher zu kopieren. Leider werden die Details hierfür auch vom Rust-Sprachteam nicht explizit angegeben. Was folgt, ist meine persönliche Interpretation. Ich denke, was ich sage, ist unumstritten, sofern nicht anders angegeben, aber das könnte natürlich ein falscher Eindruck sein.
Untypisierte / byteweise Kopie
Wenn ein Bereich von Bytes kopiert wird, überschreibt der Quellbereich im Allgemeinen nur den Zielbereich. Wenn der Quellbereich also "0x00 0xUU 0xUU 0xUU" war, enthält der Zielbereich nach dem Kopieren genau diese Liste von Bytes.
So verhält sich memcpy
/ memmove
in C (in meiner Interpretation des Standards, was hier leider nicht sehr klar ist). Führt in Rust ptr::copy{,_nonoverlapping}
wahrscheinlich eine byteweise Kopie durch, die derzeit jedoch nicht genau spezifiziert ist, und einige Leute möchten möglicherweise sagen, dass sie ebenfalls eingegeben wurde. Dies wurde in dieser Ausgabe etwas diskutiert .
Typisierte Kopie
Die Alternative ist eine "typisierte Kopie", die bei jeder normalen Zuweisung ( =
) und bei der Übergabe von Werten an / von einer Funktion auftritt. Eine typisierte Kopie interpretiert den Quellspeicher eines bestimmten Typs T
und "serialisiert" diesen Wert des Typs dann erneut T
in den Zielspeicher.
Der Hauptunterschied zu einer byteweisen Kopie besteht darin, dass Informationen verloren gehen, die für den Typ nicht relevant T
sind. Dies ist im Grunde eine komplizierte Art zu sagen, dass eine getippte Kopie das Auffüllen "vergisst" und es effektiv auf nicht initialisiert zurücksetzt. Im Vergleich zu einer untypisierten Kopie verliert eine getippte Kopie mehr Informationen. Nicht typisierte Kopien behalten die zugrunde liegende Darstellung bei, typisierte Kopien behalten nur den dargestellten Wert bei.
Selbst wenn Sie auf umwandeln 0usize
, PaddingDemo
kann eine getippte Kopie dieses Werts diesen Wert auf "0x00 0xUU 0xUU 0xUU" (oder andere mögliche Bytes für das Auffüllen) zurücksetzen - vorausgesetzt, data
der Offset 0 befindet sich, was nicht garantiert ist (fügen #[repr(C)]
Sie hinzu, wenn Sie möchten diese Garantie).
In Ihrem Fall wird ptr::write
ein Argument vom Typ verwendet PaddingDemo
, und das Argument wird über eine typisierte Kopie übergeben. Bereits zu diesem Zeitpunkt können sich die Füllbytes beliebig ändern, insbesondere können sie 0xUU werden.
Nicht initialisiert usize
Ob Ihr Code UB hat, hängt dann von einem weiteren Faktor ab, nämlich ob ein nicht initialisiertes Byte in a usize
UB ist. Die Frage ist, ob ein (teilweise) nicht initialisierten Speicherbereich repräsentiert eine ganze Zahl? Derzeit ist dies nicht der Fall und somit gibt es UB . Ob dies der Fall sein sollte, wird jedoch heftig diskutiert, und es ist wahrscheinlich, dass wir dies irgendwann zulassen werden.
Viele andere Details sind jedoch noch unklar - zum Beispiel kann die Umwandlung von "0x00 0xUU 0xUU 0xUU" in eine Ganzzahl durchaus zu einer vollständig nicht initialisierten Ganzzahl führen, dh Ganzzahlen können möglicherweise die "teilweise Initialisierung" nicht beibehalten. Um teilweise initialisierte Bytes in Ganzzahlen beizubehalten, müssten wir grundsätzlich sagen, dass eine Ganzzahl keinen abstrakten "Wert" hat, sondern nur eine Folge von (möglicherweise nicht initialisierten) Bytes. Dies spiegelt nicht wider, wie Ganzzahlen in Operationen wie verwendet werden /
. (Ein Teil davon hängt auch von LLVM-Entscheidungen ab poison
undfreeze
; LLVM kann entscheiden, dass beim Laden eines Integer-Typs das Ergebnis vollständig ist, poison
wenn ein Eingabebyte vorhanden istpoison
.) Selbst wenn der Code nicht UB ist, weil wir nicht initialisierte Ganzzahlen zulassen, verhält er sich möglicherweise nicht wie erwartet, da die Daten, die Sie übertragen möchten, verloren gehen.
Wenn Sie Rohbytes übertragen möchten, empfehle ich, einen dafür geeigneten Typ zu verwenden, z MaybeUninit
. Wenn Sie einen Integer-Typ verwenden, sollte das Ziel darin bestehen, Integer-Werte zu übertragen, dh Zahlen.