Zuweisen von weniger Speicher als die angegebene Größe eines Zeigers auf ein Array

Lesezeit: 6 Minuten

Benutzer-Avatar
Jackson Allan

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?

  • 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ür foo = malloc(sizeof(int[5])); (Aber nicht bei der Verwendung 10 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 *fooSie werden dem Compiler gesagt haben, dass es eine gibt int [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

Benutzer-Avatar
dbusch

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.

  • Dies ist ein [language-lawyer] Frage, wäre es angebracht, Zitate zu den Abschnitten der Spezifikation aufzunehmen, die diese Position unterstützen.

    – Johannes Bollinger

    31. Oktober 2021 um 15:38 Uhr

  • @JohnBollinger: Ich habe das Language-Lawyer-Tag hinzugefügt, weil es in der Natur der Frage lag, aber OP sollte ihr Interesse bestätigen oder ablehnen.

    – Eric Postpischil

    31. Oktober 2021 um 15:40 Uhr


  • Ah. Danke für die Klarstellung, @EricPostpischil.

    – Johannes Bollinger

    31. Oktober 2021 um 15:42 Uhr

  • @EricPostpischil Ich denke, das Language-Lawyer-Tag passt hier 🙂 Ich interessiere mich für diesen speziellen Punkt und was die Standards dazu sagen, und finde keine Lösung für ein tangential verwandtes Problem.

    – Jackson Allan

    31. Oktober 2021 um 15:44 Uhr

  • Ich widerspreche dem der Ausdruck *foo was Typ hat int[10] greift auf ein Objekt dieses Typs zu nicht mehr als mit int bar[10] der Ausdruck bar greift auf das Objekt nämlich überhaupt nicht zu, da *foo ebenso gut wie bar werden implizit in einen Zeiger auf das erste Element umgewandelt, bevor ein Zugriff stattfinden könnte.

    – Armali

    18. November 2021 um 19:58 Uhr

Benutzer-Avatar
John Bollinger

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) > nund o wird über einen lvalue vom Typ gelesen oder geschrieben T. Dann schreibt Absatz 6.5/6 den effektiven Typ zu T widersprechen oaber 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.)

  • Dies ist die richtige Antwort. Das undefinierte Verhalten kommt von a Fallendarstellung.

    – Höhenflug

    1. November 2021 um 10:31 Uhr

  • Ich bin nicht der Meinung, dass dies als Fallendarstellung qualifiziert wird. Nur weil ein Lese- oder Schreibvorgang über den zugewiesenen Speicher hinausgeht, heißt das nicht, dass die Lese-/Schreib-Darstellung eine Trap-Darstellung ist. Angenommen, ein int ein Zweierkomplement ohne Füllbits ist, gibt es keine Trap-Darstellung, ob der Zugriff auf nicht zugeordneten Speicherplatz erfolgt oder nicht.

    – dbusch

    1. November 2021 um 15:40 Uhr

  • @dbush, es ist keine Trap-Darstellung im verallgemeinerten Sinne. Tatsächlich gibt es keine von einem Datentyp unabhängige Trap-Darstellung. Aber wenn die Darstellung des zugewiesenen Objekts als Wert vom Typ interpretiert wird TDann ist es Notwendig eine Fallendarstellung dieses Typs (behaupte ich), weil sie zu klein ist, um anders zu sein. Andernfalls müssen wir eine dritte Kategorie erfinden, weder gültige Repräsentation noch Trap-Repräsentation, und wir brauchen auch eine andere Grundlage in der Spezifikation für den Zugriff mit UB.

    – Johannes Bollinger

    1. November 2021 um 15:50 Uhr

  • Es geht nicht um das Lesen oder Schreiben im Besonderen; es geht um die Größe des Objekts, auf das zugegriffen wird.

    – Johannes Bollinger

    1. November 2021 um 15:54 Uhr

  • @alinsoar, wie diese Antwort bereits sagt, ist die C-Definition “Trap-Darstellung” in C17-Abschnitt 3.19.4 angegeben. Das heißt: “eine Objektdarstellung, die keinen Wert des Objekttyps darstellen muss”. Das ist es. Sie werden erkennen, dass dies am Anfang des oben zitierten Absatzes 6.2.6.1/5 wiederholt wird. Wenn Sie mit “konkret verknüpft” nach einer Assoziation mit Hardwarearchitektur oder -verhalten fragen, gibt es keine konkrete Verknüpfung. Es gibt auch keinen konkreten Link für vieles andere in der Spezifikation. Das Erstellen solcher Links ist die Aufgabe von Implementierungen.

    – Johannes Bollinger

    1. November 2021 um 21:40 Uhr


1333770cookie-checkZuweisen von weniger Speicher als die angegebene Größe eines Zeigers auf ein Array

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

Privacy policy