Pytanie Wymagania dotyczące zachowania wskaźnika na zmiennym wskazaniu obiektu nieulotnego


C11 6.7.3 Kwalifikatory typów, paragraf 7, brzmi:

Obiekt, który ma typ o wartościach lotnych, może być modyfikowany w sposób nieznany do implementacji lub mieć inne nieznane skutki uboczne. Dlatego każde wyrażenie odnoszące się do takiego przedmiotu należy oceniać ściśle zgodnie z zasadami maszyny abstrakcyjnej, jak opisano w 5.1.2.3.

W poniższym przykładzie, czy obiekt jest dostępny w trzeciej linii z zastrzeżeniem powyższej reguły?

int x;
volatile int *p = &x;
*p = 42;

Innymi słowy, czy fakt, że lwartość *p ma typ volatile int oznacza, że ​​dostęp do lotnego obiektu jest możliwy, lub robi to p wskazuje na obiekt nieulotny x oznacza, że ​​kompilator może zoptymalizować tę wiedzę i ominąć niestabilny dostęp?

Ponieważ może to być interesujące, szczególny przypadek użycia, który mnie interesuje, wykracza poza zakres zwykłego C; obejmuje atomizację w celu synchronizacji nici za pomocą konstruktów pre-C11 (które mogą być inline asm lub po prostu uważane za czarną skrzynkę) dla porównania atomowego i zamiany, z następującym idiomem:

do {
    tmp = *p;
    new = f(tmp);
} while (atomic_cas(p, tmp, new) != success);

Tutaj wskaźnik p miałbym typ volatile int *, ale jestem zaniepokojony tym, co dzieje się, gdy rzeczywiście wskazany obiekt jest nieulotny, szczególnie, czy kompilator może przekształcić pojedynczy dostęp do *p od tmp = *p w dwa dostępy w następującej formie:

do {
    new = f(*p);
} while (atomic_cas(p, *p, new) != success);

co oczywiście uczyniłoby kod niepoprawny. Dlatego celem jest ustalenie, czy wszystkie takie wskazane obiekty rzeczywiście muszą być volatile int.


11
2018-02-22 04:06


pochodzenie


Program dopasowujący nie będzie w stanie wykryć różnicy, jako dostęp i modyfikacja x nie są zauważalnymi działaniami niepożądanymi. Dlatego kompilator może zoptymalizować pod kontrolą reguły as-if. - Igor Tandetnik
@IgorTandetnik: W ten sposób skłaniam się również do interpretowania tego, ale nie wydaje się to popularną teorią, i wciąż zastanawiam się, czy to prawda. Gdyby x nigdy nie jest dostępny, z wyjątkiem jako *(volatile int *)&x, i &x jest widoczne dla sprzętu, który uzyskuje do niego dostęp, lub jeśli sig_atomic_t jest int i *(volatile int *)&x jest dostępny od obsługi sygnału, to czy są tam możliwe do zaobserwowania efekty uboczne? - R..
Jeśli sprzęt uzyskuje dostęp do xi deklarujesz, że jest nieulotna, wówczas twoja konfiguracja sprzętowa nie implementuje maszyny wirtualnej C. - philipxy
@supercat Nie sądzę, że rozumiem kwestię, którą próbujesz stworzyć; pytanie definiuje nielotny int x ale mówisz o obiekcie o zmiennej lotności. Dla deklaracja  extern int x norma mówi: "Jeżeli podjęta zostanie próba odniesienia się do obiektu zdefiniowanego za pomocą typu o zmiennej lotności poprzez użycie lwartości o typie nieulotnym, zachowanie jest niezdefiniowane." Standard nie wspiera niestabilności w niepełnym wymiarze godzin. - philipxy
@supercat PS Mogę sobie wyobrazić parę kompilatora i linkera, która obsługuje implementację pliku źródłowego o zmiennym volatile i nieulotnego pliku x soruce z pojedynczego obiektu niestabilnego w niepełnym wymiarze godzin. Ale taki kompilator-plus-linker nie obsługuje semantyki standardu dla tej pary plików źródłowych definiujących program "a" c. - philipxy


Odpowiedzi:


Aktualizacja 18 lutego 2017 r

Odpowiedź poniżej przytacza i omawia język w Standardie, jakiś sprzeczny język w Uzasadnieniu, a niektóre komentarze od gnu.cc są sprzeczne. Istnieje raport o usterce, który zasadniczo zawiera zgodę komisji (choć wciąż otwartą), którą Standard powinien powiedzieć, i że intencją zawsze była i że implementacje zawsze odzwierciedlały, że nie jest to zmienność obiekt to ma znaczenie (według standardu), ale zmienności (wartość l) dostępu (zgodnie z uzasadnieniem). (Podziękowania dla Olaf za wzmiankę o tym DR.)

Podsumowanie raportu o defekcie dla C11 wersja 1.10 Data: kwiecień 2016 Przemiana słaba semantyki DR 476 dla wartości l 04/2016 Otwarte


Nie. Ponieważ dostęp do obiektu nie jest niestabilny.

Obiekt p jest wskaźnikiem typu na volatile int. Ale x nie jest przedmiotem typu o zmiennej lotności. Kwalifikacje na p wpływają na to, do czego można uzyskać dostęp przez to, ale nie wpływają na typ obiektu, który wskazuje. Nie ma ograniczeń w dostępie do obiektu niekwalifikowanego poprzez lotną lwartość. Zatem dostęp do x przez p nie jest dostępem do obiektu typu o zmiennej lotności.

(Patrz 6.7.3 Kwalifikatory typów dla ograniczeń dostępu do obiektów o kwalifikowanych typach, po prostu nie można uzyskać dostępu do zmiennego obiektu kwalifikowanego za pomocą nieokreślonej lwartości).

Z drugiej strony, ten post cytaty z 6.7.3 uzasadnienia dla International Standard - Programming Languages ​​- C:

Rzucanie wartości do kwalifikowanego typu nie ma żadnego efektu; kwalifikacja   (volatile, say) może nie mieć wpływu na dostęp, ponieważ wystąpił   przed tą sprawą. Jeśli konieczne jest uzyskanie dostępu do nieulotnego obiektu   używając lotnej semantyki, technika polega na rzuceniu adresu   obiekt do odpowiedniego typu wskaźnik do kwalifikowanego, a następnie dereferencja   ten wskaźnik.

Jednak nie mogę znaleźć języka w standardzie, który mówi, że semantyka jest oparta na typie lwartości. Od gnu.org:

Jednym z obszarów zamieszania jest rozróżnienie między obiektami zdefiniowanymi   rodzaje lotne i lotne wartości l. Z punktu widzenia standardu C   w widoku obiekt zdefiniowany zmiennym typem jest widoczny z zewnątrz   zachowanie. Można myśleć o takich obiektach jak o małym oscyloskopie   sond dołączone do nich, aby użytkownik mógł obserwować niektóre właściwości   dostępu do nich, tak jak użytkownik może obserwować dane zapisane   pliki wyjściowe. Jednak standard nie wyjaśnia, czy   użytkownicy mogą obserwować dostęp do zmiennych przedmiotów za pomocą lotnych wartości l.

[..] ze standardu nie jest jasne, czy lotne wartości l   generalnie więcej gwarancji niż nielotne wartości l, jeżeli   ukryte obiekty są zwyczajne.

Nie, ponieważ nie ma żadnych skutków ubocznych:

Nawet jeśli semantyka *p musi być niestabilna, jednak standard mówi:

5.1.2.3 Wykonanie programu 4 W maszynie abstrakcyjnej są wszystkie wyrażenia   oceniane według określonej semantyki. Rzeczywista implementacja   nie musi oceniać części wyrażenia, jeśli może wywnioskować, że jest   wartość nie jest używana i nie są potrzebne żadne efekty uboczne   (w tym wszelkie spowodowane wywołaniem funkcji lub uzyskaniem dostępu do zmiennej   obiekt).

Ponownie, w twoim kodzie nie ma niestabilnego obiektu. Chociaż jednostka kompilująca, która widzi tylko p nie można dokonać tej optymalizacji.

Pamiętaj też

6.7.3 Kwalifikatory typu 7 [...] To, co stanowi dostęp do obiektu o typie z lotnością kwalifikowaną, jest definiowane przez implementację.

5.1.2.3 Wykonanie programu 8 Każda implementacja może określać bardziej ścisłe powiązania między abstrakcyjną i faktyczną semantyką.

Zatem zwykłe pojawianie się zmiennych wartości l nie mówi ci, jakie są "dostępy". Nie masz prawa mówić o "jednym dostępie do *p od tmp = *p"z wyjątkiem udokumentowanego zachowania implementacyjnego.


7
2018-02-22 06:46



Cytat z uzasadnienia wydaje się wskazywać na intencję, która różni się od tekstu normatywnego ... :-( - R..
Nawet jeśli obiekt jest "zwyczajny", sugerowałbym, że konieczne jest umożliwienie przerwania, innych wątków itp., Aby uzyskać do niego dostęp za pomocą volatile semantyki, z zastrzeżeniem, że nieulotne dostępy mogą zachowywać się w nieoczekiwany sposób. Istnieją sytuacje w świecie rzeczywistym, w których kod musi alokować bufory w czasie wykonywania i mieć dostęp do nich za pomocą zmiennej semantyki. Jeśli bufor został zwrócony z malloc zostały wzięte pod uwagę volatiledostępy za pomocą nielotnych wskaźników wywoływałyby UB, więc taki bufor musi być "zwyczajny", ale jeśli zwyczajność ... - supercat
... bufor oznacza, że ​​kompilatory mogą zignorować volatile kwalifikatory na wskaźnikach do ich zawartości, semantyka niezbędna do współdzielenia danych między kontekstami byłaby nieosiągalna. - supercat
@supercat Nie ma takiej możliwości. Jeśli zachowuje się w sposób współdzielony, musisz zadeklarować, że jest niestabilny. Tak powiesz kompilatorowi, że zachowuje się w ten sposób. To jest definicja języka, w przeciwnym razie kompilator może założyć, że nie jest dzielony / niestabilny. Używasz UB (niezdefiniowane zachowanie) w tył. Kiedy standard mówi, że jest niezdefiniowany, jest niezdefiniowany i program może zrobić wszystko; po prostu opisujesz program, dla którego sprzęt nie zachowuje się tak, jak mówi język, że powinien się zachowywać. - philipxy
@philipxy: Jakim mechanizmem można kodować dynamicznie alokować pamięć dla procesu, który musi mieć volatile semantyki, jeśli nie za pomocą malloc? - supercat


Nie do końca pewne, ale myślę, że chodzi o różnicę między typem obiektu ma i typem obiektu określone z.

Z C11 (n1570) 6.3.2.1 p1 (pominięty przypis, emph mine:)

Na lwartość jest wyrażeniem (z typem obiektu innym niż void), który potencjalnie oznacza obiekt; jeśli lwartość nie wskazuje obiektu podczas jego oceny, zachowanie jest niezdefiniowane. Kiedy mówi się, że dany obiekt ma określony typ, typ jest określony przez lwartość użytą do oznaczenia obiektu. [...]

Jest to l-wartość, która definiuje typ obiektu dla danego dostępu. W przeciwieństwie, *p nie oznacza obiektu określone lotny. Na przykład ibid. 6.7.3 p6 (em. Moje) czyta

[...] Jeśli podejmie się próbę odniesienia się do obiektu określone z typem o zmiennej lotności poprzez zastosowanie wartości l z typem nieulotnym, zachowanie jest niezdefiniowane.133)

133) Dotyczy to tych obiektów, które zachowują się tak, jakby zostały zdefiniowane z kwalifikowanymi typami, nawet jeśli nie są faktycznie zdefiniowane jako obiekty w programie (np. Obiekt w odwzorowanym w pamięci adresie wejścia / wyjścia).

Jeśli celem było umożliwienie zoptymalizowanego kodu, prawdopodobnie cytat z tego pytania byłby czytelny Obiekt, który ma [jest zdefiniowany za pomocą] typu o lotnych kwalifikacjach może być modyfikowany [...]

"Definicja" identyfikatora*) jest zdefiniowany w 6.7, ust. 5.

Ibid. 6.7.3 p7 (To, co stanowi dostęp do obiektu o typie o zmiennej lotności, jest definiowane przez implementację.) daje pewną swobodę implementatorom, ale dla mnie intencją wydaje się, że efekt uboczny modyfikacji obiektu oznaczony n należy uznać za możliwe do zaobserwowania dzięki zgodnej implementacji.

*) Afaik standard nie definiuje "obiektu zdefiniowanego przez (pewien typ)" w dowolnym miejscu, więc czytam go jako "obiekt oznaczony identyfikatorem zadeklarowanym (przy pewnym typie) według definicji".


2
2018-02-22 12:53



Skłaniam się ku zaakceptowaniu tej interpretacji na podstawie analizy słowa "ma". Porównywalny język dla const pojawia się w definicji "modyfikowalnej lwartości" tam, gdzie "nie ma" mieć typ const. "W takim przypadku, jeśli" have "odnosiło się do typu, którego obiekt został zadeklarowany w definicji, a nie do typu użytego wyrażenia, *(const int *)&x byłaby możliwa do zmienienia wartość l, co oczywiście nie jest zamiarem. Istnieje pewna różnica między "obiektem, który ma" i "wyrażeniem ... potencjalnie oznaczającym obiekt, który nie ma" - R..
ale nadal wydaje mi się rozsądne, że "przedmiot" w tekście, który pierwotnie zacytowałem, jest obiektem wyznaczonym przez lwartość, mającą typ wyrażenia l-wartości. - R..
Byłbym nieufny wobec analizy "porównywalnego języka"; standard został wyprodukowany przez wiele osób za pośrednictwem wielu edycji i wydaje się mało prawdopodobne, że dołożyliby starań, by zsynchronizować język dla tych dwóch miejsc - M.M