Was ist die Semantik überlappender Objekte in C?

Lesezeit: 5 Minuten

Benutzer-Avatar
BeeOnRope

Betrachten Sie die folgende Struktur:

struct s {
  int a, b;
};

Typisch1hat diese Struktur die Größe 8 und die Ausrichtung 4.

Was ist, wenn wir zwei erstellen struct s Objekte (genauer gesagt schreiben wir zwei solcher Objekte in den zugewiesenen Speicher), wobei das zweite Objekt das erste überlappt?

char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4

// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};

printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);

Ist irgendetwas an diesem Programm undefiniertes Verhalten? Wenn ja, wo wird es undefiniert? Wenn es nicht UB ist, wird garantiert immer Folgendes gedruckt:

o2.a=3
o2.b=4
o1.a=1
o1.b=3

Insbesondere möchte ich wissen, was mit dem Objekt passiert, auf das gezeigt wird o1 Wenn o2, die es überlappt, geschrieben. Ist es immer noch erlaubt, auf den unverschlüsselten Teil zuzugreifen (o1->a)? Greift auf den geclobbered Teil zu o1->b einfach das gleiche wie zugreifen o2->a?

Wie funktioniert effektiver Typ hier bewerben? Die Regeln sind klar genug, wenn Sie von nicht überlappenden Objekten und Zeigern sprechen, die auf denselben Ort wie das letzte Geschäft zeigen, aber wenn Sie anfangen, über den effektiven Typ von Teilen von Objekten oder überlappenden Objekten zu sprechen, sind sie weniger klar.

Würde sich etwas ändern, wenn der zweite Schreibvorgang von einem anderen Typ wäre? Wenn die Mitglieder sagen würden int und short statt zwei ints?

Hier ist ein Gottriegel wenn du dort damit spielen willst.


1 Diese Antwort gilt auch für Plattformen, auf denen dies nicht der Fall ist: Einige könnten beispielsweise Größe 4 und Ausrichtung 2 haben. Auf einer Plattform, auf der Größe und Ausrichtung gleich wären, würde diese Frage nicht zutreffen, da ausgerichtete, überlappende Objekte dies wären unmöglich, aber ich bin mir nicht sicher, ob es eine solche Plattform gibt.

  • Ich bin mir ziemlich sicher, dass es UB ist, aber ich lasse einen Sprachanwalt Kapitel und Verse liefern.

    – Barmar

    7. April 2020 um 0:27 Uhr

  • Ich denke, dass der C-Compiler auf den alten Cray-Vektorsystemen erzwungen hat, dass Ausrichtung und Größe gleich sind, mit einem ILP64-Modell und erzwungener 64-Bit-Ausrichtung (Adressen sind 64-Bit-Wörter – keine Byte-Adressierung). Das führte natürlich zu vielen anderen Problemen….

    – John D. McCalpin

    9. April 2020 um 21:46 Uhr

Im Grunde ist dies alles Graubereich im Standard; Die strikte Aliasing-Regel legt grundlegende Fälle fest und überlässt es dem Leser (und Compiler-Anbietern), die Details einzugeben.

Es gab Bemühungen, eine bessere Regel zu schreiben, aber bisher haben sie zu keinem normativen Text geführt, und ich bin mir nicht sicher, welchen Status dies für C2x hat.

Wie in meiner Antwort auf Ihre vorherige Frage erwähnt, ist die häufigste Interpretation diese p->q meint (*p).q und die effektiver Typ gilt für alle *pauch wenn wir uns dann weiter bewerben .q .

Unter dieser Deutung printf("o1.a=%d\n", o1->a); würde undefiniertes Verhalten als die verursachen effektiver Typ des Standorts *o1 ist nicht s (da ein Teil davon überschrieben wurde).

Die Begründung für diese Interpretation kann in einer Funktion gesehen werden wie:

void f(s* s1, s* s2)
{
    s2->a = 5;
    s1->b = 6;
    printf("%d\n", s2->a);
}

Mit dieser Interpretation konnte die letzte Zeile optimiert werden puts("5"); aber ohne sie müsste der Compiler berücksichtigen, dass der Funktionsaufruf möglicherweise gewesen wäre f(o1, o2); und verlieren daher alle Vorteile, die angeblich durch die strenge Aliasing-Regel bereitgestellt werden.

Ein ähnliches Argument gilt für zwei nicht verwandte Strukturtypen, die beide zufällig eine haben int Mitglied mit unterschiedlichem Offset.

  • Mit f(s* s1, s* s2)ohne restrictkann der Compiler nicht annehmen s1 und s2 sind verschiedene Zeiger. ich denkenwieder ohne restrict, es kann nicht einmal davon ausgegangen werden, dass sie sich nicht teilweise überlappen. IAC, ich sehe nicht, dass das Anliegen von OP gut demo’d ist f() Analogie. Viel Glück beim Entwirren. UV für die erste Hälfte.

    – chux – Wiedereinsetzung von Monica

    7. April 2020 um 1:45 Uhr

  • @chux-ReinstateMonica ohne Einschränkung, s1 == s2 wäre erlaubt, aber keine teilweise Überlappung. (Die Optimierung in meinem Codebeispiel könnte immer noch durchgeführt werden, wenn s1 == s2)

    – MM

    7. April 2020 um 2:00 Uhr

  • @chux-ReinstateMonica Sie könnten das gleiche Problem auch mit just in Betracht ziehen int anstelle von Structs (und ein System mit _Alignof(int) < sizeof(int)).

    – MM

    7. April 2020 um 2:28 Uhr

  • Der Status dieser Art von Frage bezüglich des effektiven Typs für C2x ist ziemlich offen und wird in der Studiengruppe noch diskutiert. Seien Sie jedoch vorsichtig mit der Behauptung der Gleichwertigkeit von p->q und (*p).q. Dies mag für die von Ihnen angegebene Typinterpretation zutreffen, aus betrieblicher Sicht jedoch nicht. Für gleichzeitige Zugriffe auf dieselbe Struktur ist es wichtig, dass ein Zugriff eines Mitglieds nicht den Zugriff eines anderen Mitglieds impliziert.

    – Jens Gustedt

    7. April 2020 um 7:50 Uhr

  • Strikte Aliasing-Regel ist ungefähr Zugang. Ausdruck der linken Seite in der E1.E2 Ausdruck führt keinen Zugriff durch (ich meine die ganze E1 Ausdruck. Einige seiner Unterausdrücke können einen Zugriff ausführen. Dh wenn E1 ist (*p)dann Lesen des Zeigerwerts beim Auswerten p ist Zugriff, sondern Auswertung von *p oder (*p) führt keinen Zugriff durch). Die strenge Aliasing-Regel gilt nicht, wenn kein Zugriff besteht.

    – Sprachanwalt

    7. April 2020 um 11:54 Uhr


1346070cookie-checkWas ist die Semantik überlappender Objekte in C?

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

Privacy policy