Dostęp do chronionego członka poprzez member-pointer: czy to hack?

Wszyscy wiemy, że elementy określone protected z klasy bazowej mogą być dostępne tylko z własnej instancji klasy pochodnej. Jest to cecha ze standardu, która była wielokrotnie omawiana na Stack Overflow:

Ale wydaje się możliwe obejście tego ograniczenia za pomocą wskaźników członkowskich, ponieważ użytkownik chtz pokazał mi :

struct Base { protected: int value; };
struct Derived : Base
{
    void f(Base const& other)
    {
        //int n = other.value; // error: 'int Base::value' is protected within this context
        int n = other.*(&Derived::value); // ok??? why?
        (void) n;
    }
};

Demo na żywo na coliru

Dlaczego jest to możliwe, czy jest to pożądana cecha, czy usterka gdzieś w implementacji lub brzmieniu standardu?


Z komentarzy wyłoniło się kolejne pytanie: Jeśli Derived::f jest wywołana z rzeczywistym Base, czy to nieokreślone zachowanie?

Author: lucidbrot, 2018-03-29

4 answers

Fakt, że członek nie jest dostępny przy użyciu class member access expr.ref (aclass.amember) ze względu na kontrolę dostępu [klasy.access] nie czyni tego elementu niedostępnym przy użyciu innych wyrażeń.

Wyrażenie &Derived::value (którego typem jest int Base::*) jest całkowicie zgodny ze standardem i oznacza element value z Base. Następnie wyrażenie a_base.*p Gdzie p jest wskaźnikiem do członka Base i a_base instancja Base jest również zgodny ze standardem .

Tak więc każdy kompilator zgodny ze standardem tworzy wyrażenie other.*(&Derived::value); zdefiniowane zachowanie: dostęp do elementu value z other.

 28
Author: Oliv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-29 10:42:24
Czy to włamanie?

W podobny sposób jak używanie reinterpret_cast, może to być niebezpieczne i potencjalnie może być źródłem trudnych do znalezienia błędów. Ale jest dobrze uformowany i nie ma wątpliwości, czy powinien działać.

Dla wyjaśnienia analogii: zachowanie reinterpret_cast jest również dokładnie określone w standardzie i może być używane bez żadnego UB. Ale reinterpret_cast omija system typów, a system typów istnieje nie bez powodu. Podobnie, ten wskaźnik do członka trick jest dobrze uformowany zgodnie ze standardem, ale omija enkapsulację prętów, a to enkapsulacja (typowo) istnieje nie bez powodu (mówię typowo, bo przypuszczam, że programista może używać enkapsulacji frywolnie).

[czy to] usterka gdzieś w implementacji czy sformułowaniu standardu?

Nie, implementacja jest poprawna. W ten sposób został określony język.

Funkcja członka Derived może oczywiście uzyskać dostęp &Derived::value, ponieważ jest chronionego członka bazy.

Wynikiem tej operacji jest wskaźnik do członka Base. Można to zastosować do odniesienia do Base. Uprawnienia dostępu dla członków nie mają zastosowania do wskaźników dla członków: mają zastosowanie tylko do nazw członków.


Z komentarzy pojawiło się kolejne pytanie: jeśli Pochodna:: f jest wywoływana z rzeczywistą bazą, to czy jest to nieokreślone zachowanie?

Nie UB. Base ma członka.
 14
Author: user2079303,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-29 10:24:41

Zasadniczo to, co robisz, to oszukiwanie kompilatora, i to powinno zadziałać. Zawsze widzę tego rodzaju pytania i ludzie czasami mają złe wyniki, a czasami działa, w zależności od tego, jak to konwertuje do kodu asemblera.

Pamiętam, że widziałem przypadek ze słowem kluczowym const Na liczbie całkowitej, ale potem jakimś podstępem facet był w stanie zmienić wartość i skutecznie obejść świadomość kompilatora. Rezultatem było: Błędna wartość dla prostej matematycznej operacja. Powód jest prosty: Assembly w x86 robi rozróżnienie między stałymi i zmiennymi, ponieważ niektóre instrukcje zawierają stałe w swoim kodzie opcode. Tak więc, ponieważ kompilator uważa, że jest stałą, potraktuje ją jako stałą i poradzi sobie z nią w zoptymalizowany sposób z niewłaściwą instrukcją procesora, a baam, masz błąd w wynikowej liczbie.

Innymi słowy: kompilator będzie próbował wyegzekwować wszystkie reguły, które może wyegzekwować, ale prawdopodobnie możesz w końcu oszukać go, i może lub nie może uzyskać złe wyniki w oparciu o to, co próbujesz zrobić, więc lepiej zrobić takie rzeczy tylko wtedy, gdy wiesz, co robisz.

W Twoim przypadku wskaźnik &Derived::value może być obliczony na podstawie liczby bajtów od początku klasy. Jest to w zasadzie sposób, w jaki kompilator uzyskuje do niego dostęp, więc kompilator:

  1. nie widzi żadnego problemu z uprawnieniami, ponieważ uzyskujesz dostęp do value poprzez derived w czas kompilacji.
  2. można to zrobić, ponieważ w obiekcie o tej samej strukturze co derived (NO, oczywiście, base) bierzemy przesunięcie w bajtach.
Więc nie łamiesz żadnych zasad. Udało Ci się obejść zasady kompilacji. Nie powinieneś tego robić, dokładnie z powodów opisanych w linkach, które załączasz, ponieważ łamie to enkapsulację OOP, ale cóż, jeśli wiesz, co robisz...
 -1
Author: The Quantum Physicist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-29 08:42:11

Żeby dodać do odpowiedzi i przybliżyć trochę horror, który mogę przeczytać między twoimi wierszami. Jeśli widzisz, że access specifiiers to "prawo", które chroni Cię przed robieniem "złych rzeczy", myślę, że o to ci chodzi. public, protected, private, const ... są częścią systemu, który jest ogromnym plusem dla C++. Języki bez niego mogą mieć wiele zalet, ale gdy budujesz duże systemy, takie rzeczy są prawdziwym atutem.

Mówiąc, że: myślę, że to dobrze, że można uzyskać wokół prawie wszystkich siatek zabezpieczających dostarczonych do ciebie. O ile pamiętacie, że "możliwe" nie znaczy "dobre". Dlatego nigdy nie powinno to być "łatwe". Ale co do reszty-to zależy od Ciebie. Jesteś architektem.

Lata temu mogłem po prostu to zrobić (i nadal może działać w niektórych środowiskach):

#define private public

Bardzo pomocny dla' wrogich ' zewnętrznych plików nagłówkowych. Dobra praktyka? Co o tym myślisz? Ale czasami opcje są ograniczone.

Więc tak, to co pokazujesz jest rodzajem naruszenia w systemie. Ale hej, co powstrzymuje cię przed pozyskiwaniem i rozdawaniem publicznych odniesień do członka? Jeśli straszne problemy z konserwacją Cię włączają-za wszelką cenę, dlaczego nie?

 -1
Author: Bert Bril,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-04 12:08:33