Rozważ następującą strukturę:
struct s {
int a, b;
};
Zazwyczaj 1 , ta struktura będzie miała rozmiar 8 i wyrównanie 4.
Co jeśli utworzymy dwa struct s
obiekty (a ściślej zapisujemy do przydzielonej pamięci dwa takie obiekty), przy czym drugi obiekt nakłada się na pierwszy?
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);
Czy coś w tym programie jest niezdefiniowane? Jeśli tak, to gdzie staje się niezdefiniowane? Jeśli nie jest to UB, to zawsze gwarantuje wydruk następujących informacji:
o2.a=3
o2.b=4
o1.a=1
o1.b=3
W szczególności chcę wiedzieć, co dzieje się z obiektem wskazywanym przez o1
moment o2
, w którym nakłada się on na siebie. Czy nadal można uzyskiwać dostęp do niezakleszczonej części ( o1->a
)? Czy dostęp do części zablokowanej jest o1->b
po prostu taki sam jak dostęp o2->a
?
W jaki sposób stosuje się tutaj typ skuteczny ? Reguły są wystarczająco jasne, gdy mówimy o nie nakładających się obiektach i wskaźnikach, które wskazują na to samo miejsce co ostatni sklep, ale kiedy zaczynasz mówić o efektywnym typie części obiektów lub nakładających się obiektów, jest to mniej jasne.
Czy cokolwiek by się zmieniło, gdyby drugi zapis był innego typu? Jeżeli członkowie byli powiedzieć, int
a short
raczej niż dwa int
s?
Oto godbolt, jeśli chcesz się z nim pobawić.
1 Ta odpowiedź dotyczy również platform, na których tak nie jest: np. Niektóre mogą mieć rozmiar 4 i wyrównanie 2. Na platformie, w której rozmiar i wyrównanie były takie same, pytanie nie miałoby zastosowania, ponieważ wyrównane, nakładające się obiekty być niemożliwe, ale nie jestem pewien, czy istnieje taka platforma.
Odpowiedzi:
Zasadniczo jest to wszystko szara strefa w standardzie; reguła ścisłego aliasingu określa podstawowe przypadki i pozostawia czytnikowi (i dostawcom kompilatora) wypełnienie szczegółów.
Podjęto wysiłki, aby napisać lepszą regułę, ale jak dotąd nie przyniosły one żadnego tekstu normatywnego i nie jestem pewien, jaki jest jej status dla C2x.
Jak wspomniano w mojej odpowiedzi na poprzednie pytanie, najczęstszą interpretacją jest
p->q
to,(*p).q
że skuteczny typ odnosi się do wszystkich*p
, nawet jeśli później będziemy stosować.q
.Zgodnie z tą interpretacją,
printf("o1.a=%d\n", o1->a);
spowoduje zachowanie niezdefiniowane jako skuteczny rodzaj lokalizacji*o1
nie jests
(ponieważ część z nich została nadpisana).Uzasadnienie tej interpretacji można zobaczyć w funkcji takiej jak:
Dzięki tej interpretacji można zoptymalizować ostatnią linię
puts("5");
, ale bez niej kompilator musiałby wziąć pod uwagę, że wywołanie funkcji mogło być,f(o1, o2);
a zatem utracić wszystkie korzyści, które rzekomo zapewnia reguła ścisłego aliasingu.Podobny argument dotyczy dwóch niepowiązanych typów struktur, które oba mają elementy o
int
różnym przesunięciu.źródło
f(s* s1, s* s2)
, bezrestrict
, kompilator nie może zakładaćs1
is2
są różne wskaźniki. I pomyśleć , ponownie bezrestrict
, nie może nawet założyć, że nie pokrywają się częściowo. IAC, nie widzę, byf()
analogia przedstawiała obawy OP . Powodzenia, niesienie. UV na pierwszą połowę.s1 == s2
byłoby dozwolone, ale nie częściowe nakładanie się. (Optymalizacja w moim przykładzie kodu może być nadal wykonana, jeślis1 == s2
)int
zamiast zamiast struktur (i systemu z_Alignof(int) < sizeof(int)
).p->q
i(*p).q
. Może to być prawdą dla interpretacji typu, jak twierdzisz, ale nie jest to prawdą z operacyjnego punktu widzenia. Dla równoczesnego dostępu do tej samej struktury ważne jest, aby dostęp członka nie oznaczał dostępu żadnego innego członka.E1.E2
wyrażeniu nie wykonuje dostępu (mam na myśli całeE1
wyrażenie. Niektóre z jego podwyrażeń mogą wykonywać dostęp. ToE1
znaczy(*p)
, jeśli jest , to odczyt wartości wskaźnika podczas ocenyp
jest dostępem, ale ocena*p
lub(*p)
nie wykonuje żadnego dostęp). Ścisła reguła aliasingu nie ma zastosowania w przypadku braku dostępu.