Reinterpret_cast ne zaman kullanılır?


461

Ben küçük uygulanabilirliği ile karıştı reinterpret_castvs static_cast. Ne okudum genel kurallar türleri derleme zamanında dolayısıyla kelime yorumlanabilir zaman statik döküm kullanmaktır static. Bu, C ++ derleyicisinin dahili dökümler için dahili olarak kullandığı dökümdür.

reinterpret_casts iki senaryoda uygulanabilir:

  • tamsayı türlerini işaretçi türlerine ve tam tersini dönüştürme
  • bir işaretçi türünü diğerine dönüştürür. Anladığım kadarıyla genel fikir, bunun taşınabilir olmadığı ve kaçınılması gerektiğidir.

Biraz karışık nerede ihtiyacım olan bir kullanım, C + + C C deniyorum ve C kodu temelde bir tutar C ++ nesnesine tutunması gerekiyor void*. void *Sınıf türü arasında dönüştürme yapmak için hangi oyuncular kullanılmalıdır ?

İkisinin de kullanımını gördüm static_castve reinterpret_cast? Okuduğum şeyden static, döküm derleme zamanında olabileceğinden daha iyi görünüyor olsa da ? reinterpret_castBir işaretçi türünden diğerine dönüştürmek için kullanmasına rağmen ?


10
reinterpret_castçalışma zamanında olmaz. Her ikisi de derleme zamanı ifadeleridir. Kaynaktan en.cppreference.com/w/cpp/language/reinterpret_cast : "farklı static_cast ama const_cast gibi reinterpret_cast ifadesi herhangi bir CPU talimatlarına derlememektedir Bu sadece bit dizisini tedavisinde derleyici yönlendiren bir derleyici yönergesidir. (nesne temsili) ifadesini, new_type türünde olduğu gibi ifade eder. "
Cris Luengo

@ HeretoLearn, * .c ve * .cpp dosyasından ilgili kod parçalarını eklemek mümkün müdür? Sorunun açıklanmasını geliştirebileceğini düşünüyorum.
OrenIshShalom

Yanıtlar:


443

C ++ standardı aşağıdakileri garanti eder:

static_castvoid*adrese ve işaretçi kullanarak adres korunur. Yani, aşağıda olduğu a, bve caynı adrese işaret ediyor:

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

reinterpret_castyalnızca bir işaretçiyi farklı bir türe çevirirseniz ve daha sonra reinterpret_castorijinal türe geri döndürürseniz orijinal değeri elde edeceğinizi garanti eder . Yani aşağıda:

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

ave caynı değeri içerir, ancak değeri bbelirtilmez. (pratikte genellikle aynı adresi içerecek ave cfakat o standardında belirtilen değil ve daha karmaşık hafıza sistemleriyle makinelerde doğru olmayabilir.)

Ve yayın yapmak için void*, static_casttercih edilmelidir.


18
'B' nin tanımsız olması gerçeğini seviyorum. Onunla aptalca şeyler yapmanı engelliyor. Başka bir işaretçi türüne bir şey yaparsanız, sorun istersiniz ve ona güvenemeyeceğiniz gerçeği sizi daha dikkatli hale getirir. Yukarıda static_cast <> kullansaydınız 'b' ne işe yarar?
Martin York

3
Ben reinterpret_cast <> aynı bit desen garanti düşündüm. (başka bir tür için geçerli bir işaretçi ile aynı değildir).
Martin York

37
bkullanıldığında C ++ 11'de değeri artık belirtilmez reinterpret_cast. Ve C ++ 03'te bir döküm int*için void*birlikte yapılması yasaktı reinterpret_cast(dolayısıyla 11 C ++ için değiştirildi derleyiciler ve pratik olduğunu uygulamak vermedi rağmen).
Johannes Schaub - litb

55
Bu aslında "reinterpret_cast ne zaman kullanılacağı" sorusuna cevap vermez.
einpoklum

6
@LokiAstari Bence belirtilmemiş, aptalca şeyler yapmanı engellemiyor. Sadece belirtilmediğini hatırladığınızda sizi durdurur. Büyük farklılık. Şahsen ben belirtmedim. Hatırlamak için çok fazla.
Helin Wang

158

reinterpret_castGerektiğinde bir vaka opak veri tipleriyle arayüz oluştururken ortaya çıkar. Bu, programlayıcının üzerinde kontrolü olmayan satıcı API'larında sık sık görülür. Aşağıda, bir satıcının keyfi küresel verileri depolamak ve almak için bir API sağladığı anlaşmalı bir örnek verilmiştir:

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

Bu API'yı kullanmak için programcı verilerini VendorGlobalUserDatatekrar tekrar yayınlamalıdır. static_castişe yaramazsa, kullanmanız gerekir 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;
}

Aşağıda örnek API'nın tutarlı bir uygulaması yer almaktadır:

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

7
Evet, bu düşünebileceğim tek anlamlı reinterpret_cast kullanımı hakkında.
jalf

8
Bu geç bir soru olabilir, ancak satıcı API'sı neden bunu kullanmıyor void*?
Xeo

19
@Xeo void * kullanmazlar, çünkü derleme zamanında (bazı) tip denetimini kaybederler.
jesup

4
"Opak" veri türlerinin pratik kullanım durumu, bir API'yi C'ye maruz bırakmak, ancak uygulamayı C ++ 'da yazmak istediğiniz zamandır. YBÜ bunu birkaç yerde yapan bir kütüphane örneğidir. Örneğin, parodi denetleyicisi API, yazının işaretçiler ile anlaşma USpoofChecker*, USpoofCheckerboş bir yapı olduğunu. Bununla birlikte, kaputun altında, her geçtiğinizde USpoofChecker*, reinterpret_castdahili bir C ++ tipine geçer.
sffc

@sffc neden C yapı türünü kullanıcıya göstermiyorsunuz?
Gupta

101

Kısa cevap: Ne anlama geldiğini bilmiyorsanız reinterpret_castkullanmayın. Gelecekte buna ihtiyacınız olacaksa, bileceksiniz.

Tam cevap:

Temel sayı türlerini ele alalım.

Örneğin dönüştürdüğünüzde int(12)için unsigned float (12.0f)hem sayı olarak bazı hesaplamalar çağırmak için işlemci ihtiyaçları farklı bit temsil edilmektedir. Bunun anlamı budur static_cast.

Öte yandan, reinterpret_castCPU çağırdığınızda herhangi bir hesaplama başlatmaz. Bellekteki başka bir bit gibi davranır. Dönüştürmek Yani int*için float*bu anahtar kelime ile, (işaretçi dereferecing sonra) yeni değer matematiksel anlam eski değerle ilgisi yoktur.

Örnek: Tekreinterpret_castbir nedenden dolayı (endianness) taşınabilir olmadığıdoğrudur. Ancak bu genellikle şaşırtıcı bir şekilde onu kullanmanın en iyi nedenidir. Örneği düşünelim: dosyadan ikili 32 bit numarasını okumak zorundasınız ve bunun büyük endian olduğunu biliyorsunuz. Kodunuzun genel olması ve büyük endian (örneğin bazı ARM) ve küçük endian (örn. X86) sistemlerinde düzgün çalışması gerekir. Yani bayt sırasını kontrol etmelisiniz. Derleme zamanında iyi bilinir, böylece constexprişlev yazabilirsiniz : Bunu başarmak için bir işlev yazabilirsiniz:

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

Açıklama:x belleğinikili gösterimi0000'0000'0000'0001(büyük) veya0000'0001'0000'0000(küçük endian) olabilir. Yeniden yorumlama işleminden sonrapişaretçialtındaki baytsırasıyla0000'0000veyaolabilir0000'0001. Statik döküm kullanıyorsanız,0000'0001hangi endianite kullanılırsa kullanılsınher zaman olacaktır.

DÜZENLE:

İlk versiyonunda benim örnek işlevi yapılmış is_little_endianolması constexpr. En yeni gcc'de (8.3.0) iyi derlenir, ancak standart yasadışı olduğunu söylüyor. Clang derleyicisi onu derlemeyi reddeder (bu doğrudur).


1
Güzel örnek! Ben insan için daha az belirsiz hale uint16_t ve uint8_t için imzasız char kısa yerine.
Jan Turoň

@ JanTuroň doğru, shortbellekte 16 bit sürdüğünü varsayamayız . Düzeltildi.
jaskmar

1
Örnek yanlış. constexpr işlevlerinde reinterpret_cast'e izin verilmiyor
Michael Veksler

1
Her şeyden önce, bu kod hem en son clang (7.0.0) hem de gcc (8.2.0) tarafından reddedilir. Ne yazık ki biçimsel dilde sınırlama bulamadım. Tek bulabildiğim social.msdn.microsoft.com/Forums/vstudio/en-US/…
Michael Veksler

2
Daha spesifik olarak, en.cppreference.com/w/cpp/language/constant_expression (madde 16), reinterpret_cast'in sabit bir ifadede kullanılamayacağını açıkça belirtir. Ayrıca, reinterpret_cast'i açıkça dışlayan github.com/cplusplus/draft/blob/master/papers/N3797.pdf (5.19 sabit ifade) sayfalarına125-126 bakın. Sonra 7.1.5 constexpr belirleyici öğesi 5 (sayfa 146) * Şablon olmayan, varsayılan olmayan bir constexpr işlevi için ... eğer bir çekirdek sabit ifadenin değerlendirilmiş bir alt ifadesi olabilir ... ), program kötü biçimlendirilmiş *
Michael Veksler

20

Anlamı reinterpret_castC ++ standardı tarafından tanımlanmamıştır. Bu nedenle, teorik olarak a reinterpret_castprogramınızı çökertebilir. Pratikte derleyiciler beklediğiniz şeyi yapmaya çalışırlar, bu da geçmekte olduğunuz şeylerin parçalarını, yayınlamakta olduğunuz türdeymiş gibi yorumlamaktır. Kullanacağınız derleyicilerin ne yapacağını biliyorsanız, reinterpret_cast bunu kullanabilirsiniz, ancak taşınabilir olduğunu söylemek yalan söylemek olacaktır.

Açıkladığınız vaka ve düşünebileceğiniz hemen hemen her durumda bunun yerine veya başka bir alternatif reinterpret_castkullanabilirsiniz static_cast. Diğer şeylerin yanı sıra, standardın beklentileriniz hakkında şunları söylemesi gerekir static_cast(§5.2.9):

“Pointer to cv void” türünün bir değeri açıkça bir işaretçiye nesne türüne dönüştürülebilir. “Pointer to cv void” e dönüştürülen ve orijinal işaretçi tipine geri döndürülen nesne tipi tip değerinin orijinal değeri olacaktır.

Bu nedenle, kullanım durumunuz için, standardizasyon komitesinin kullanmanız için tasarlandığı oldukça açık görünüyor static_cast.


5
Programınızın çökmesine neden olmaz. Standart, reinterpret_cast hakkında birkaç garanti sunmaktadır. İnsanların beklediği kadar değil.
jalf

1
Düzgün kullanırsanız değil. Yani, A'dan B'ye A'ya reinterpret_cast mükemmel şekilde güvenlidir ve iyi tanımlanmıştır. Ancak B'nin değeri tanımlanmamıştır ve evet, buna güvenirseniz kötü şeyler olabilir. Ancak, yalnızca standardın izin verdiği şekilde kullandığınız sürece, dökümün kendisi yeterince güvenlidir. ;)
jalf

55
lol, reinterpret_crash gerçekten programınızın çökmesine şüpheli. Ancak reinterpret_cast olmayacak. ;)
jalf

5
<irony> Derleyicimde denedim ve bir şekilde derlemeyi reddetti reinterpret_crash. Hiçbir derleyici hata yeniden yorumlama programı çökmesini engel olmaz. En kısa sürede bir hata rapor edeceğim! </irony>
paercebal

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

12

Reinterpret_cast'in bir kullanımı da (IEEE 754) şamandıralara bitsel işlemler uygulamak istemenizdir. Buna bir örnek Hızlı Ters Karekök hüneriydi:

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

Şamandıranın ikili temsilini bir tamsayı olarak ele alır, sağa kaydırır ve bir sabitten çıkarır, böylece üssü yarıya indirir ve reddeder. Bir şamandıraya dönüştükten sonra, bu yaklaşımı daha kesin hale getirmek için Newton-Raphson yinelemesine tabi tutulur:

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;
}

Bu başlangıçta C dilinde yazılmıştır, bu nedenle C dökümleri kullanır, ancak benzer C ++ dökümü reinterpret_cast'tir.


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
Standart bunun tanımlanmamış bir davranış olduğunu söylüyor: en.cppreference.com/w/cpp/language/reinterpret_cast ("tür örtüşme" altında)
Cris Luengo

@CrisLuengo ben yerine bütün reinterpret_castile memcpy, halen UB değil mi?
sandthorn

@sandthorn: Bu standarda göre UB, ancak mimariniz için işe yarıyorsa, endişelenmeyin. Sanırım Intel mimarileri için herhangi bir derleyici için sorun yok. Diğer mimarilerde amaçlandığı gibi (hatta çöktü) işe yaramadı - örneğin, yüzer ve uzun ürünlerin ayrı bellek bölmelerinde saklanması mümkün olabilir (böyle bir mimariyi bilmediğimden, bu sadece bir argüman ...) . memcpykesinlikle yasal olur.
Cris Luengo


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

Sonuçlandırmaya çalıştım ve şablonlar kullanarak basit bir güvenli döküm yazdım. Bu çözümün, işlevlere işaretçi atmayı garanti etmediğini unutmayın.


1
Ne? Neden rahatsız oluyorsun? Bu tam olarak ne olduğunu reinterpret_castzaten bu durumda yapar: ". Bir nesne işaretçisi açıkça farklı bir türde bir nesne işaretçisi dönüştürülebilir [72] Bir prvalue v nesne işaretçi tipi nesnesi işaretçisi türüne dönüştürülür‘için işaretçi cv T ’, sonuç static_cast<cv T*>(static_cast<cv void*>(v)). " - N3797.
underscore_d

Gelince c++2003Standart I yapabilirsiniz DEĞİL bulmak reinterpret_castyaparstatic_cast<cv T*>(static_cast<cv void*>(v))
Sasha Zezulinsky

1
Tamam, doğru, ama 13 yıl önceki bir sürüm umurumda değil ve (büyük olasılıkla) eğer onlardan kaçınamazlarsa çoğu kodlayıcı olmamalıdır. Cevaplar ve yorumlar aksi belirtilmedikçe mevcut en son Standardı yansıtmalıdır ... IMHO. Her neyse, sanırım Komite bunu 2003'ten sonra açıkça ekleme gereği duydu. (Çünkü IIRC, C ++ 11'de aynıydı)
underscore_d

Önce C++03öyleydi C++98. Tonlarca proje taşınabilir C yerine eski C ++ kullandı. Bazen taşınabilirliği önemsemeniz gerekir. Örneğin, Solaris, AIX, HPUX, Windows için aynı kodu desteklemeniz gerekir. Derleyici bağımlılığı ve taşınabilirliği söz konusu olduğunda zor. Bir taşınabilirlik cehennemi tanıtmak için iyi bir örnek reinterpret_castkodunuzda bir kullanmaktır
Sasha Zezulinsky

Yine, benim gibi, kendinizi sadece dilin en son ve en iyi sürümü ile iyi oynayan platformlarla sınırlamaktan mutluluk duyuyorsanız, itirazınız tartışmalı bir noktadır.
underscore_d

1

Öncelikle int gibi belirli bir türde bazı verileriniz var:

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

Sonra float gibi başka bir değişkenle aynı değişkene erişmek istersiniz:

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

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

veya

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

//this could be used in c and cpp

KISA: Bu, aynı belleğin farklı bir tip olarak kullanıldığı anlamına gelir. Böylece, yukarıdaki gibi int türü olan şamandıraların ikili temsillerini şamandıralara dönüştürebilirsiniz. Örneğin 0x80000000 -0'dır (mantis ve üs boştur ancak msb işareti birdir. Bu aynı zamanda çiftler ve uzun çiftler için de geçerlidir.

OPTIMIZE: c-döküm pointeraritmetik tarafından yapılırken reinterpret_cast birçok derleyici optimize olacağını düşünüyorum (değer belleğe kopyalanması gerekir, çünkü işaretçiler işlemci kayıt olamazdı).

NOT: Her iki durumda da, dökümden önce döküm değerini bir değişkene kaydetmelisiniz! Bu makro yardımcı olabilir:

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

"Aynı belleğin farklı bir tip olarak kullanıldığı anlamına gelir" ama belirli tip çiftlerle sınırlıdır. Senin örneğin olarak reinterpret_castforma intiçin float&tanımsız davranıştır.
jaskmar

1

Kullanmanın bir nedeni reinterpret_cast, bir temel sınıfın bir vtable içermediği, ancak türetilmiş bir sınıfın olmasıdır. Bu durumda static_castve reinterpret_castfarklı işaretçi değerleriyle sonuçlanacaktır (bu yukarıdaki jalf tarafından belirtilen atipik durum olacaktır ). Bir feragatname olarak, bunun standardın bir parçası olduğunu değil, birkaç yaygın derleyicinin uygulanmasını belirtiyorum.

Örnek olarak, aşağıdaki kodu alın:

#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;
}

Hangi gibi bir çıktı üretir:

& b = 0x7ffe10e68b38
a = 0x7ffe10e68b40
ar = 0x7ffe10e68b38
fark = 2

Denediğim tüm derleyicilerde (MSVC 2015 ve 2017, clang 8.0.0, gcc 9.2, icc 19.0.1 - son 3 için godbolt'a bakın ) sonucu 2'den (MSVC için 4) static_castfarklıdır reinterpret_cast. Fark hakkında uyarmak için tek derleyici clang'dı:

17:16: uyarı: 'B *' sınıfından sıfır olmayan ofset 'A *' da tabanına 'reinterpret_cast' 'static_cast' [-Wreinterpret-base-class]
const A * ar = reinterpret_cast (& b) ;
^ ~~~~~~~~~~~~~~~~~~~~~~~
17:16: not:
const A * ar = reinterpret_cast (& b) uyandırırken işaretçiyi doğru ayarlamak için 'static_cast' kullanın ;
^ ~~~~~~~~~~~~~~~
static_cast

Son bir uyarı, temel sınıfın veri üyesi yoksa (örn. int i;) Clang, gcc ve icc , olduğu reinterpret_castgibi aynı adresi döndürürken static_castMSVC hala yoktur.


1

İşte Avi Ginsburg'un reinterpret_castChris Luengo, flodin ve cmdLP tarafından bahsedilen özelliğini açıkça gösteren bir çeşidi: derleyicinin sivri hafıza konumuna yeni tipte bir nesne gibi davranması:

#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);
}

Bunun sonucu şöyle olur:

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.

B nesnesinin bellekte önce B'ye özgü veriler, ardından gömülü A nesnesi olarak oluşturulduğu görülebilir. static_castDoğru katıştırılmış bir nesnenin adresini geri yarattığı ve gösterici static_castdoğru veri alanının değerini verir. İşaretçi tarafından oluşturulan işaretçi , düz A nesnesi gibi reinterpret_castdavranır bve böylece işaretçi veri alanını almaya çalıştığında, bu alanın içeriğiymiş gibi B'ye özgü bazı veriler döndürür.

Bunun bir kullanımı, reinterpret_castbir işaretçiyi işaretsiz bir tam sayıya dönüştürmektir (işaretçiler ve işaretsiz tamsayılar aynı boyutta olduğunda):

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


-6

Hızlı cevap: static_castderlenirse kullanın , aksi takdirde başvurunuz reinterpret_cast.


-16

SSS bölümünü okuyun ! C ++ verilerinin C'de tutulması riskli olabilir.

C ++ 'da, bir nesneye yönelik bir işaretçi void *hiçbir döküm olmadan dönüştürülebilir . Ama bunun tersi doğru değil. static_castOrijinal işaretçiyi geri almak için a'ya ihtiyacınız vardır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.