Kiedy używać reinterpret_cast?


459

Jestem trochę mylić z stosowalności reinterpret_castvs static_cast. Z tego, co przeczytałem, ogólne zasady używają rzutowania statycznego, gdy typy mogą być interpretowane w czasie kompilacji, stąd słowo static. Jest to rzutowanie, którego kompilator C ++ używa wewnętrznie do rzutowania niejawnego.

reinterpret_castmają zastosowanie w dwóch scenariuszach:

  • konwertuj typy liczb całkowitych na typy wskaźników i odwrotnie
  • przekonwertować jeden typ wskaźnika na inny. Ogólny pomysł, jaki mam, jest taki, że nie można go przenosić i należy go unikać.

Tam, gdzie jestem trochę zdezorientowany, potrzebuję jednego użycia, wywołuję C ++ z C, a kod C musi trzymać się obiektu C ++, więc w zasadzie zawiera on void*. Jakiej obsady należy użyć do konwersji między void *typem a klasą?

Widziałem użycie obu static_casti reinterpret_cast? Chociaż z tego, co czytałem, wydaje się staticto lepsze, ponieważ obsada może się zdarzyć w czasie kompilacji? Chociaż mówi się, aby użyć reinterpret_castdo konwersji z jednego typu wskaźnika na inny?


9
reinterpret_castnie dzieje się w czasie wykonywania. Oba są instrukcjami kompilacji. From en.cppreference.com/w/cpp/language/reinterpret_cast : "W przeciwieństwie do static_cast, ale podobnie jak const_cast, wyrażenie reinterpret_cast nie kompiluje się z żadnymi instrukcjami procesora. Jest to wyłącznie dyrektywa kompilatora, która instruuje kompilator, aby traktował sekwencję bitów (reprezentacja obiektu) wyrażenia, jakby miało typ nowy_typ. "
Cris Luengo

@HeretoLearn, czy można dodać odpowiednie fragmenty kodu z pliku * .c i * .cpp? Myślę, że może to poprawić ekspozycję pytania.
OrenIshShalom

Odpowiedzi:


442

Standard C ++ gwarantuje:

static_castwprowadzenie wskaźnika do i z void*zachowuje adres. Oznacza to, że w dalszej części a, ba cwszystko wskazuje na ten sam adres:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_castgwarantuje tylko, że jeśli rzutujesz wskaźnik na inny typ, a następnie z reinterpret_castpowrotem na typ oryginalny , otrzymujesz oryginalną wartość. Więc w następujący sposób:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

ai czawierają tę samą wartość, ale wartość nie bjest określona. (w praktyce zwykle będzie zawierał ten sam adres co ai c, ale nie jest to określone w standardzie, i może nie być prawdziwe na komputerach z bardziej złożonymi systemami pamięci).

Do castowania do iz void* , static_castpowinno być preferowane.


18
Podoba mi się fakt, że „b” jest niezdefiniowane. To powstrzymuje cię przed robieniem głupich rzeczy. Jeśli rzucisz coś na inny typ wskaźnika, pytasz o problemy, a fakt, że nie możesz na nim polegać, czyni cię bardziej ostrożnym. Jeśli używałeś static_cast <> powyżej, jaki użytek to i tak „b”?
Martin York

3
Myślałem, że reinterpret_cast <> gwarantuje ten sam wzór bitowy. (co nie jest tym samym, co poprawny wskaźnik do innego typu).
Martin York

37
wartość bnie jest już nieokreślona w C ++ 11 podczas używania reinterpret_cast. A w C ++ 03 zabroniono wykonywania rzutowania int*na (chociaż kompilatory tego nie implementowały i było to niepraktyczne, dlatego zmieniono na C ++ 11). void*reinterpret_cast
Johannes Schaub - litb

55
To tak naprawdę nie odpowiada na pytanie „kiedy użyć reinterpret_cast”.
einpoklum 15.04.16

6
@LokiAstari Myślę, że nieokreślony nie powstrzymuje cię przed robieniem głupich rzeczy. Zatrzymuje cię tylko wtedy, gdy pamiętasz, że jest nieokreślony. Duża różnica. Osobiście nie lubię nieokreślonego. Za dużo do zapamiętania.
Helin Wang

158

Jednym z przypadków, w których reinterpret_castjest to konieczne, jest połączenie z nieprzezroczystymi typami danych. Zdarza się to często w interfejsach API dostawców, nad którymi programista nie ma kontroli. Oto wymyślony przykład, w którym dostawca udostępnia interfejs API do przechowywania i wyszukiwania dowolnych danych globalnych:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

Aby użyć tego interfejsu API, programista musi przesyłać swoje dane do VendorGlobalUserDataiz powrotem. static_castnie będzie działać, należy użyć reinterpret_cast:

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

int main() {
    MyUserData u;

        // store global data
    VendorGlobalUserData d1;
//  d1 = &u;                                          // compile error
//  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error
    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    VendorSetUserData(d1);

        // do other stuff...

        // retrieve global data
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // compile error
//  p = static_cast<MyUserData *>(d2);                // compile error
    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

Poniżej znajduje się przemyślana implementacja przykładowego interfejsu API:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

7
Tak, to jedyne sensowne użycie reinterpret_cast, jakie mogę wymyślić.
lipiec

8
To może być późne pytanie, ale dlaczego interfejs API dostawcy tego nie używa void*?
Xeo

19
@Xeo Nie używają void *, ponieważ tracą (niektóre) sprawdzanie typu w czasie kompilacji.
jesup

4
Praktycznym przykładem użycia „nieprzezroczystych” typów danych jest sytuacja, gdy chcesz wystawić API na C, ale napisać implementację w C ++. ICU to przykład biblioteki, która robi to w kilku miejscach. Na przykład w interfejsie API sprawdzania fałszowania zajmujesz się wskaźnikami typu USpoofChecker*, gdzie USpoofCheckerjest pustą strukturą. Jednak pod maską, za każdym razem, gdy przechodzisz USpoofChecker*, przechodzi reinterpret_castdo wewnętrznego typu C ++.
sffc

@ sffc dlaczego nie ujawnić użytkownikowi typu C struct?
Gupta,

101

Krótka odpowiedź: jeśli nie wiesz coreinterpret_cast oznacza skrót, nie używaj go. Jeśli będziesz go potrzebować w przyszłości, będziesz wiedział.

Pełna odpowiedź:

Rozważmy podstawowe typy liczb.

Podczas konwersji na przykład int(12)na unsigned float (12.0f)procesor należy wykonać pewne obliczenia, ponieważ obie liczby mają różne odwzorowanie bitów. To właśnie static_castoznacza.

Z drugiej strony, podczas wywoływania reinterpret_castprocesor nie wywołuje żadnych obliczeń. Po prostu traktuje zestaw bitów w pamięci tak, jakby miał inny typ. Więc po konwersji int*do float*z tego słowa kluczowego, nowa wartość (po wskaźnik dereferecing) nie ma nic wspólnego ze starą wartość w matematycznym znaczeniu.

Przykład: Prawdą jest, żereinterpret_castnie jest przenośny z jednego powodu - kolejności bajtów (endianness). Ale często jest to zaskakująco najlepszy powód do korzystania z niego. Wyobraźmy sobie przykład: musisz odczytać binarną 32-bitową liczbę z pliku i wiesz, że to duży endian. Twój kod musi być ogólny i działa poprawnie na systemach big endian (np. Niektóre ARM) i little endian (np. X86). Musisz więc sprawdzić kolejność bajtów. Jest dobrze znany z czasu kompilacji, więc możesz napisać constexprfunkcję: Możesz napisać funkcję, aby to osiągnąć:

/*constexpr*/ bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

Objaśnienie: binarna reprezentacjaxw pamięci może być0000'0000'0000'0001(duża) lub0000'0001'0000'0000(mała endian). Po reinterpretacji rzutowania bajtem podpwskaźnikiem może być odpowiednio0000'0000lub0000'0001. Jeśli użyjesz castingu statycznego, zawsze tak będzie0000'0001 , bez względu na to, jaki endianizm jest używany.

EDYTOWAĆ:

W pierwszej wersji zrobiłem przykładową funkcję is_little_endianbycia constexpr. Kompiluje się dobrze na najnowszym gcc (8.3.0), ale standard mówi, że jest nielegalny. Kompilator clang nie chce go skompilować (co jest poprawne).


1
Niezły przykład! Zamienię skrót na uint16_t i unsigned char na uint8_t, aby był mniej niejasny dla człowieka.
Jan Turoň

@ JanTuroň prawda, nie możemy założyć, że shortzajmuje 16 bitów pamięci. Poprawione
jaskmar

1
Przykład jest zły. reinterpret_cast nie jest dozwolony w funkcjach constexpr
Michael Veksler

1
Przede wszystkim ten kod jest odrzucany zarówno przez najnowszy clang (7.0.0), jak i gcc (8.2.0). Niestety nie znalazłem ograniczenia w języku formalnym. Wszystko, co mogłem znaleźć, to social.msdn.microsoft.com/Forums/vstudio/en-US/…
Michael Veksler

2
Mówiąc dokładniej, en.cppreference.com/w/cpp/language/constant_expression (pozycja 16) wyraźnie stwierdza, że ​​reinterpret_cast nie może być używany w ciągłym wyrażeniu. Zobacz także github.com/cplusplus/draft/blob/master/papers/N3797.pdf (wyrażenia stałe 5.19) strony 125-126, które wyraźnie wykluczają reinterpretację. Następnie 7.1.5 Element specyfikatora constexpr 5 (strona 146) * W przypadku niezawierającej szablonu, domyślnej funkcji constexpr ... jeśli nie istnieją wartości argumentu takie, które ... mogłyby być ocenionym podwyrażeniem podstawowego wyrażenia stałego (5.19 ), program jest źle sformułowany *
Michael Veksler,

20

Znaczenie reinterpret_castnie jest zdefiniowane przez standard C ++. Dlatego teoretycznie reinterpret_castmoże to spowodować awarię programu. W praktyce kompilatory starają się robić to, czego oczekujesz, czyli interpretować bity tego, co przekazujesz, tak jakby były typem, na który rzucasz. Jeśli wiesz, jakie kompilatory zamierzasz używać, reinterpret_cast możesz go użyć, ale powiedzieć, że jest przenośny , kłamałoby.

W przypadku, który opisujesz, a właściwie w każdym przypadku, który możesz rozważyć reinterpret_cast, możesz static_castzamiast tego użyć innej alternatywy. Między innymi standard ma to do powiedzenia na temat tego, czego możesz oczekiwać static_cast(§5.2.9):

Wartość typu „wskaźnik na cv void” można jawnie przekonwertować na wskaźnik na typ obiektu. Wartość wskaźnika typu na obiekt konwertowana na „wskaźnik na cv void” iz powrotem na oryginalny typ wskaźnika będzie miała swoją oryginalną wartość.

Tak więc w twoim przypadku użycia wydaje się dość jasne, że komitet normalizacyjny zamierzał z niego skorzystać static_cast.


5
Nie całkiem zawiesza twój program. Standard oferuje kilka gwarancji dotyczących reinterpret_cast. Po prostu nie tak wielu, jak ludzie często się spodziewają.
lipiec

1
Nie, jeśli użyjesz go poprawnie. Oznacza to, że reinterpretacja od A do B na A jest całkowicie bezpieczna i dobrze zdefiniowana. Ale wartość B jest nieokreślona i tak, jeśli na tym polegasz, mogą się zdarzyć złe rzeczy. Ale sama obsada jest wystarczająco bezpieczna, o ile używasz jej tylko w sposób zgodny z normą. ;)
lipiec

55
lol, podejrzewam, że reinterpret_crash może rzeczywiście zawiesić twój program. Ale reinterpret_cast nie będzie. ;)
lipiec

5
<irony> Wypróbowałem to na moim kompilatorze i jakoś odmówiłem kompilacji reinterpret_crash. W żaden sposób błąd kompilatora nie powstrzyma mnie przed awarią mojego programu reinterpretacji.
Zgłoszę błąd JAK

18
@paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }

12

Jednym z zastosowań reinterpret_cast jest użycie operacji bitowych na liczbach zmiennoprzecinkowych (IEEE 754). Jednym z przykładów jest sztuczka Fast Inverse Square-Root:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

Traktuje binarną reprezentację liczby zmiennoprzecinkowej jako liczbę całkowitą, przesuwa ją w prawo i odejmuje od stałej, tym samym zmniejszając o połowę i negując wykładnik potęgi. Po konwersji z powrotem na liczbę zmiennoprzecinkową poddaje się iteracji Newtona-Raphsona w celu dokładniejszego przybliżenia:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the deuce? 
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

Zostało to pierwotnie napisane w C, więc używa rzutowań C, ale analogicznym rzutowaniem C ++ jest rzut_interpretacji.


1
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))- ideone.com/6S4ijc
Orwellophile,

1
Standard mówi, że jest to niezdefiniowane zachowanie: en.cppreference.com/w/cpp/language/reinterpret_cast (pod „typ aliasing”)
Cris Luengo

@CrisLuengo Gdybym substytut wszystkim reinterpret_castze memcpyjest to nadal UB?
sandthorn

@sandthorn: To jest UB zgodnie ze standardem, ale jeśli działa dla twojej architektury, nie martw się o to. Ta sztuczka jest odpowiednia, jak sądzę, dla dowolnego kompilatora dla architektur Intela. Nie może działać zgodnie z przeznaczeniem (lub nawet zawiesić się) na innych architekturach - na przykład możliwe jest, że zmiennoprzecinkowe i długie są przechowywane w osobnych przedziałach pamięci (nie znam żadnej takiej architektury, to tylko argument ...) . memcpyzdecydowanie uczyniłoby to legalnym.
Cris Luengo,


2
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

Próbowałem zakończyć i napisałem prostą bezpieczną obsadę za pomocą szablonów. Pamiętaj, że to rozwiązanie nie gwarantuje rzutowania wskaźników na funkcje.


1
Co? Po co się męczyć? Dokładnie tak dzieje się reinterpret_castjuż w tej sytuacji: „Wskaźnik obiektu można jawnie przekonwertować na wskaźnik obiektu innego typu. [72] Gdy wartość v typu wskaźnika obiektu jest konwertowana na typ wskaźnika obiektu„ wskaźnik na cv T ”, wynik jest static_cast<cv T*>(static_cast<cv void*>(v)). ” - N3797.
underscore_d

Co do c++2003standardu mogę nie zauważyć, że reinterpret_castrobistatic_cast<cv T*>(static_cast<cv void*>(v))
Sasha Zezulinsky

1
OK, prawda, ale nie obchodzi mnie wersja sprzed 13 lat i większość koderów nie powinna, jeśli (prawdopodobnie), może jej uniknąć. Odpowiedzi i komentarze powinny naprawdę odzwierciedlać najnowszy dostępny standard, chyba że określono inaczej ... IMHO. W każdym razie, sądzę, że Komitet poczuł potrzebę wyraźnego dodania tego po 2003 r. (Ponieważ IIRC było tak samo w C ++ 11)
underscore_d

Zanim C++03to było C++98. Mnóstwo projektów używało starego C ++ zamiast przenośnego C. Czasem trzeba dbać o przenośność. Na przykład musisz obsługiwać ten sam kod w systemach Solaris, AIX, HPUX, Windows. Pod względem zależności i przenośności kompilatora jest to trudne. Dobrym przykładem wprowadzenia piekła przenośności jest użycie reinterpret_castw swoim kodzie
Sasha Zezulinsky

znowu, jeśli tak jak ja z przyjemnością ograniczasz się tylko do platform, które grają ładnie z najnowszą i najlepszą wersją języka, twój sprzeciw jest sporny.
underscore_d

1

Najpierw masz jakieś dane określonego typu, takie jak int tutaj:

int x = 0x7fffffff://==nan in binary representation

Następnie chcesz uzyskać dostęp do tej samej zmiennej, co inny typ, taki jak float: możesz wybrać pomiędzy

float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

lub

float y = *(float*)&(x);

//this could be used in c and cpp

KRÓTKI: oznacza, że ​​ta sama pamięć jest używana jako inny typ. Możesz więc konwertować binarne reprezentacje liczb zmiennoprzecinkowych jako typu int jak wyżej na zmiennoprzecinkowe. 0x80000000 ma na przykład wartość -0 (mantysa i wykładnik są zerowe, ale znak msb ma wartość jeden. Działa to również w przypadku podwójnych i długich podwójnych.

OPTYMALIZACJA: Myślę, że reinterpret_cast byłby zoptymalizowany w wielu kompilatorach, podczas gdy rzutowanie c jest wykonywane przez pointerarytmikę (wartość musi zostać skopiowana do pamięci, ponieważ wskaźniki nie mogą wskazywać rejestrów cpu).

UWAGA: W obu przypadkach przed rzutowaniem należy zapisać rzutowaną wartość w zmiennej! To makro może pomóc:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })

To prawda, że ​​„oznacza to, że ta sama pamięć jest używana jako inny typ”, ale jest ograniczona do konkretnej pary typów. W twoim przykładowym reinterpret_castformularzu intto float&jest niezdefiniowane zachowanie.
jaskmar

1

Jednym z powodów reinterpret_castjest to, że klasa podstawowa nie ma vtable, ale klasa pochodna ma. W takim przypadku, static_casti reinterpret_castspowoduje różne wartości wskaźnika (byłby to nietypowy przypadek wspomniany powyżej przez jalf ). Jako oświadczenie nie stwierdzam, że jest to część standardu, ale implementacja kilku rozpowszechnionych kompilatorów.

Jako przykład weź poniższy kod:

#include <cstdio>

class A {
public:
    int i;
};

class B : public A {
public:
    virtual void func() {  }
};

int main()
{
    B b;
    const A* a = static_cast<A*>(&b);
    const A* ar = reinterpret_cast<A*>(&b);

    printf("&b = %p\n", &b);
    printf(" a = %p\n", a);
    printf("ar = %p\n", ar);
    printf("difference = %ld\n", (long int)(a - ar));

    return 0;
}

Które generuje coś takiego:

& b = 0x7ffe10e68b38
a = 0x7ffe10e68b40
ar = 0x7ffe10e68b38
różnica = 2

We wszystkich kompilatorach, które wypróbowałem (MSVC 2015 i 2017, clang 8.0.0, gcc 9.2, icc 19.0.1 - patrz godbolt dla ostatnich 3 ) wynik static_castróżni się od wyniku reinterpret_casto 2 (4 dla MSVC). Jedynym kompilatorem ostrzegającym przed różnicą był clang, z:

17:16: ostrzeżenie: „reinterpret_cast” z klasy „B *” do podstawy przy niezerowym przesunięciu „A *” zachowuje się inaczej niż „static_cast” [-Wreinterpret-base-class]
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~~~~~~~~~
17:16: Uwaga: użyj 'static_cast', aby poprawnie ustawić wskaźnik podczas upcastingu
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~
static_cast

Ostatnim zastrzeżeniem jest to, że jeśli klasa podstawowa nie ma elementów danych (np. int i;), To clang, gcc i icc zwracają ten sam adres reinterpret_castjak dla static_cast, podczas gdy MSVC nadal nie.


1

Oto wariant programu Avi Ginsburga, który wyraźnie ilustruje właściwość reinterpret_castwspomnianą przez Chrisa Luengo, flodina i cmdLP: kompilator traktuje wskazaną lokalizację pamięci tak, jakby był obiektem nowego typu:

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class A
{
public:
    int i;
};

class B : public A
{
public:
    virtual void f() {}
};

int main()
{
    string s;
    B b;
    b.i = 0;
    A* as = static_cast<A*>(&b);
    A* ar = reinterpret_cast<A*>(&b);
    B* c = reinterpret_cast<B*>(ar);

    cout << "as->i = " << hex << setfill('0')  << as->i << "\n";
    cout << "ar->i = " << ar->i << "\n";
    cout << "b.i   = " << b.i << "\n";
    cout << "c->i  = " << c->i << "\n";
    cout << "\n";
    cout << "&(as->i) = " << &(as->i) << "\n";
    cout << "&(ar->i) = " << &(ar->i) << "\n";
    cout << "&(b.i) = " << &(b.i) << "\n";
    cout << "&(c->i) = " << &(c->i) << "\n";
    cout << "\n";
    cout << "&b = " << &b << "\n";
    cout << "as = " << as << "\n";
    cout << "ar = " << ar << "\n";
    cout << "c  = " << c  << "\n";

    cout << "Press ENTER to exit.\n";
    getline(cin,s);
}

Co daje wynik w ten sposób:

as->i = 0
ar->i = 50ee64
b.i   = 0
c->i  = 0

&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i) = 00EFF978
&(c->i) = 00EFF978

&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c  = 00EFF974
Press ENTER to exit.

Można zauważyć, że obiekt B jest najpierw wbudowany w pamięć jako dane specyficzne dla B, a następnie osadzony obiekt A. static_castPoprawnie zwraca adres wbudowanego obiektu, a wskaźnik stworzony przez static_castprawidłowo daje wartość pola danych. Wskaźnik generowany przez reinterpret_castsmakołykib lokalizację pamięci tak, jakby był zwykłym obiektem A, a więc gdy wskaźnik próbuje uzyskać pole danych, zwraca niektóre dane specyficzne dla B, tak jakby była zawartością tego pola.

Jednym z zastosowań reinterpret_castjest konwersja wskaźnika na liczbę całkowitą bez znaku (gdy wskaźniki i liczby całkowite bez znaku mają ten sam rozmiar):

int i; unsigned int u = reinterpret_cast<unsigned int>(&i);


-6

Szybka odpowiedź: użyj, static_castjeśli się skompiluje, w przeciwnym razie skorzystaj z reinterpret_cast.


-16

Przeczytaj FAQ ! Przechowywanie danych C ++ w C może być ryzykowne.

W C ++ wskaźnik do obiektu można przekonwertować void *bez rzutowania. Ale to nie jest prawda na odwrót. Musisz static_castodzyskać oryginalny wskaźnik.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.