Betrachten Sie die folgende Struktur:
struct s {
int a, b;
};
Typischerweise hat 1 diese Struktur die Größe 8 und die Ausrichtung 4.
Was ist, wenn wir zwei struct s
Objekte erstellen (genauer gesagt, wir schreiben zwei solche 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 etwas 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 hingewiesen wird, o1
wenn o2
, was es überlappt, geschrieben wird. Ist es weiterhin erlaubt, auf den nicht überlasteten Teil ( o1->a
) zuzugreifen ? Ist der Zugriff auf das überlastete Teil o1->b
einfach der gleiche wie der Zugriff o2->a
?
Wie gilt hier der effektive Typ ? Die Regeln sind klar genug, wenn Sie über nicht überlappende Objekte und Zeiger sprechen, die auf dieselbe Position wie der letzte Speicher verweisen. Wenn Sie jedoch über den effektiven Typ von Teilen von Objekten oder überlappenden Objekten sprechen, ist dies 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
nicht zwei int
s?
Hier ist ein Godbolt, wenn Sie dort damit spielen möchten.
1 Diese Antwort gilt für Plattformen, auf denen dies ebenfalls nicht der Fall ist: Einige haben möglicherweise Größe 4 und Ausrichtung 2. Auf einer Plattform, auf der Größe und Ausrichtung gleich waren, würde diese Frage nicht zutreffen, da ausgerichtete, überlappende Objekte dies tun würden unmöglich sein, aber ich bin mir nicht sicher, ob es eine solche Plattform gibt.
Antworten:
Grundsätzlich ist dies alles Grauzone im Standard; Die strikte Aliasing-Regel legt grundlegende Fälle fest und überlässt es dem Leser (und den Compiler-Anbietern), die Details einzugeben.
Es wurden Anstrengungen unternommen, um eine bessere Regel zu schreiben, aber bisher haben sie keinen normativen Text ergeben, und ich bin mir nicht sicher, wie der Status für C2x lautet.
Wie in meiner Antwort auf Ihre vorherige Frage erwähnt, ist die häufigste Interpretation das
p->q
Mittel(*p).q
und der effektive Typ gilt für alle*p
, auch wenn wir uns dann weiter bewerben.q
.Nach dieser Interpretation
printf("o1.a=%d\n", o1->a);
würde dies zu undefiniertem Verhalten führen, da der effektive Typ des Standorts*o1
nicht vorhanden ists
(da ein Teil davon überschrieben wurde).Die Gründe für diese Interpretation können in einer Funktion gesehen werden wie:
Mit dieser Interpretation könnte die letzte Zeile optimiert werden
puts("5");
, aber ohne sie müsste der Compiler berücksichtigen, dass der Funktionsaufruf möglicherweisef(o1, o2);
alle Vorteile gewesen ist und daher alle Vorteile verliert, die angeblich durch die strenge Aliasing-Regel bereitgestellt werden.Ein ähnliches Argument gilt für zwei nicht verwandte Strukturtypen, bei denen beide zufällig ein
int
Element mit unterschiedlichem Versatz haben.quelle
f(s* s1, s* s2)
, ohnerestrict
kann der Compiler keine unterschiedlichen Zeiger annehmens1
unds2
sind diese. Ich denke , auch ohnerestrict
kann es nicht einmal annehmen, dass sie sich nicht teilweise überlappen. IAC, ich sehe nicht, dass die Besorgnis von OP durch dief()
Analogie gut demonstriert wird . Viel Glück beim Entwirren. UV für die erste Hälfte.s1 == s2
wäre erlaubt, aber keine teilweise Überlappung. (Die Optimierung in meinem Codebeispiel könnte noch durchgeführt werden, wenns1 == s2
)int
anstelle von Strukturen (und einem System mit_Alignof(int) < sizeof(int)
) betrachten.p->q
und beanspruchen(*p).q
. Dies mag für die von Ihnen angegebene Typinterpreation 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.E1.E2
Ausdruck führt keinen Zugriff aus (ich meine den gesamtenE1
Ausdruck. Einige seiner Unterausdrücke führen möglicherweise den Zugriff aus. Wenn dies der FallE1
ist(*p)
, wird der Zeigerwert bei der Auswertungp
als Zugriff gelesen , aber die Auswertung von*p
oder(*p)
führt keinen aus Zugriff). Die strikte Aliasing-Regel gilt nicht, wenn kein Zugriff besteht.