Ist es in C “legal”, einem Pointer-to-Array Speicher zu wenig zuzuweisen, wenn wir dann nur auf Elemente zugreifen, die in den zugewiesenen Speicher fallen? Oder ruft dies undefiniertes Verhalten hervor?
int (*foo)[ 10 ]; //Pointer to array of 10 ints
foo = malloc( sizeof( int ) * 5 ); //Under-allocation!
//Only enough memory for 5 ints
//Now we only ever access (*foo)[ 0 - 4 ]
Wenn dies an und für sich so ist nicht undefiniertes Verhalten, könnte dann der Zugriff auf ein anderes, nicht verwandtes Objekt, dessen Speicheradresse zufällig in den Adressraum des nicht zugeordneten Teils des Arrays fällt, eine strikte Aliasing-Verletzung verursachen?
Das ist undefiniertes Verhalten.
foo
soll auf ein Objekt (oder das erste eines Arrays von Objekten) des Typs zeigen int[10]
. Dies gilt als ein Objekt von Array-Typdefiniert in Abschnitt 6.2.5p20 des C-Standard
Ein Array-Typ beschreibt ein zusammenhängend zugeordnete, nicht leere Menge von Objekten mit einem bestimmten Mitgliedsobjekttyp, der als Elementtyp bezeichnet wird. Der Elementtyp muss vollständig sein, wenn der Array-Typ angegeben wird. Array-Typen werden durch ihren Elementtyp und durch die Anzahl der Elemente im Array charakterisiert. Ein Array-Typ wird von seinem Elementtyp abgeleitet, und wenn sein Elementtyp T ist, wird der Array-Typ manchmal als „Array von T“ bezeichnet. Die Konstruktion eines Array-Typs aus einem Elementtyp wird als ”Array-Typ-Ableitung” bezeichnet.
Der Teil, den ich fett hervorgehoben habe, ist der wichtige Teil. Ein int[10]
ist daher ein zusammenhängend zugewiesener Satz von 10 Objekten des Typs int
.
Sie weisen nicht genug Platz zu, so der Ausdruck *foo
was Typ hat int[10]
greift auf ein Objekt dieses Typs zu, liest dabei aber über das Ende eines zugewiesenen Speichersegments hinaus.
Wie @dbush in seiner Antwort beschreibt, ist ein Array als zusammenhängend zugewiesener, nicht leerer Satz von Objekten des Elementtyps (C17 6.2.5/20) definiert. Also klar, malloc( sizeof( int ) * 5 )
weist nicht genügend Platz für eine zu int[10]
.
Aber ich fand es schwierig, den letzten Teil dieser Antwort offiziell zu unterstützen und zu behaupten, dass der Größenunterschied (zum Beispiel) (*foo)[4]
undefiniertes Verhalten haben. Diese Schlussfolgerung erscheint plausibel, aber wo sagt die Norm das eigentlich?
Eines der Hauptprobleme dabei ist, dass (dynamisch) zugewiesene Objekte keinen deklarierten Typ haben, sondern unter Umständen einen effektiver Typ davon bestimmt, wie auf sie zugegriffen wird und wie sie aufgerufen wurden. (C17 6.5/6 und Fußnote 88). Wir wissen, dass bei Erfolg, malloc(n)
gibt einen Zeiger auf ein Objekt der Größe zurück n
(C17 7.22.3.4/2), aber wie schreiben wir undefiniertes Verhalten speziell der Assoziation mit diesem Objekt eines effektiven Typs zu, der Objekte der Größe größer als beschreibt n
?
Letztendlich entschied ich, dass der beste Weg, die Punkte zu verbinden, wie folgt ist. Nehme an, dass o
ist ein zugewiesenes Objekt der Größe n
, T
ist ein vollständiger Typ mit sizeof(T) > n
und o
wird über einen lvalue vom Typ gelesen oder geschrieben T
. Dann schreibt Absatz 6.5/6 den effektiven Typ zu T
widersprechen o
aber weil o
‘s Größe unzureichend ist, müssen wir schlussfolgern, dass seine Darstellung a darstellt Fallendarstellung des Typs T
(C17 3.19.4). Absatz 6.2.6.1/5 wiederholt dann die Definition von “Trap-Darstellung” und bringt uns dorthin, wo wir hinwollen:
Bestimmte Objektdarstellungen müssen keinen Wert des Objekttyps darstellen. Wenn der gespeicherte Wert eines Objekts eine solche Darstellung hat und von einem lvalue-Ausdruck gelesen wird, der keinen Zeichentyp hat, ist das Verhalten undefiniert. Wenn eine solche Darstellung durch einen Nebeneffekt erzeugt wird, der das gesamte oder einen Teil des Objekts durch einen Lvalue-Ausdruck ändert, der keinen Zeichentyp hat, ist das Verhalten nicht definiert. Eine solche Darstellung wird Trap-Darstellung genannt.
(Betonung hinzugefügt.)
Hmm. Interessanterweise warnt der Codeanalysator für clang-cl vor Ihrer
malloc
Linie: Warnung GED7FF984: Ergebnis von ‘malloc’ wird in einen Zeiger vom Typ ‘int [10]’, was nicht kompatibel ist mit sizeof Operandentyp ‘int’ [clang-analyzer-unix.MallocSizeof]. Und eine ähnliche Warnung fürfoo = malloc(sizeof(int[5]));
(Aber nicht bei der Verwendung10
darin).– Adrian Maulwurf
31. Oktober 2021 um 15:33 Uhr
Der native MSVC-Codeanalysator gibt keine Warnung aus. Beachten Sie jedoch, dass das Vorhandensein / Fehlen einer Diagnose (auch durch Klingeln) UB nicht (wider)beweist.
– Adrian Maulwurf
31. Oktober 2021 um 15:36 Uhr
Ich denke, technisch ist es undefiniert. In Betracht ziehen
int (*foo)[16]
und Setzen der ersten 12 Elemente auf einen bestimmten Wert. Durch den Betrieb auf*foo
Sie werden dem Compiler gesagt haben, dass es eine gibtint [16]
Objekt dort, d. h. es wurden 64 Bytes dafür reserviert. Der Compiler könnte sich also entscheiden, eine nette schnelle AVX-512-Anweisung zu verwenden, um die ersten 12 Elemente zu speichern. Diese Anweisung speichert auch die letzten 4 Elemente, aber der Compiler geht davon aus, dass dies in Ordnung ist, da ihre Werte unbestimmt sind, sodass er sie zu allem machen kann, was er will. Dann überschreibt der Compiler nicht zugeordneten Speicher. Es kann sogar sein, dass es nicht zugeordnet ist.– Eric Postpischil
31. Oktober 2021 um 15:36 Uhr
@EricPostpischil In den 80er Jahren gab es viele Universitäten, an denen Studenten C mit C-Interpretern lernten. Heutzutage ist es üblich zu sagen C-Compilerals ob die Sprache und der Compiler gleich wären :).
– Höhenflug
1. November 2021 um 11:25 Uhr
Es gibt kein in Betrieb
*foo
.– Armali
18. November 2021 um 19:40 Uhr