C'de neden uçucu madde gerekir?


Yanıtlar:


423

Uçucu derleyiciye uçucu değişkenle ilgili hiçbir şeyi optimize etmemesini söyler.

Kullanmanın en az üç yaygın nedeni vardır; bunların tümü, değişkenin değerinin görünür koddan herhangi bir işlem yapılmadan değişebileceği durumları içerir: Değerin kendisini değiştiren donanımla arabirim oluşturduğunuzda; değişkeni kullanan başka bir iş parçacığı çalıştığında; veya değişkenin değerini değiştirebilecek bir sinyal işleyici olduğunda.

Diyelim ki bir yere RAM ile eşlenen ve iki adresi olan küçük bir donanımınız var: bir komut portu ve bir veri portu:

typedef struct
{
  int command;
  int data;
  int isbusy;
} MyHardwareGadget;

Şimdi bir komut göndermek istiyorsunuz:

void SendCommand (MyHardwareGadget * gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isbusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

Kolay görünüyor, ancak derleyici veri ve komutların yazıldığı sırayı değiştirmekte özgür olduğu için başarısız olabilir. Bu, küçük gadget'ımızın önceki veri değeriyle komut vermesine neden olur. Ayrıca meşgul döngü sırasında bekleyin bir göz atın. Bu optimize edilecek. Derleyici akıllı olmaya çalışacak, isbusy değerini sadece bir kez okuyacak ve daha sonra sonsuz bir döngüye girecektir. İstediğin bu değil.

Bunu aşmanın yolu, işaretçi aracını geçici olarak bildirmektir. Bu şekilde derleyici yazdıklarınızı yapmak zorunda kalır. Bellek atamalarını kaldıramaz, kayıtlardaki değişkenleri önbelleğe alamaz ve atamaların sırasını da değiştiremez:

Bu doğru sürüm:

   void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

46
Şahsen, tamsayı boyutunun donanımla konuşurken int8 / int16 / int32 gibi açıklık olmasını tercih ederim. Güzel cevap olsa;)
tonylo

22
evet, sabit bir kayıt boyutuna sahip şeyleri beyan etmelisiniz, ama hey - bu sadece bir örnek.
Nils Pipenbrinck

69
Eşzamanlılık korumalı olmayan verilerle oynadığınızda, iş parçacığı kodunda da değişkenlik gerekir. Ve evet bunu yapmak için geçerli zamanlar var, örneğin açık eşzamanlılık korumasına ihtiyaç duymadan bir iş parçacığı güvenli dairesel mesaj kuyruğu yazabilirsiniz, ancak uçuculara ihtiyaç duyacaktır.
Gordon Wrigley

14
C spesifikasyonunu daha fazla okuyun. Uçucu, yalnızca bellek eşlemeli cihaz G / Ç veya eşzamansız bir kesme işlevinin dokunduğu bellekte tanımlanmış davranışa sahiptir. İş parçacığı hakkında hiçbir şey söylemez ve birden çok iş parçacığının dokunduğu belleğe erişimi optimize eden bir derleyici uyumludur.
ephemient

17
@tolomea: tamamen yanlış. üzgün 17 kişi bilmiyor. uçucu bir bellek çiti değildir. yalnızca görünmeyen yan etkilerin varsayımına dayalı olarak optimizasyon sırasında kod seçiminden kaçınmakla ilgilidir .
v.oddou

187

volatileC de aslında değişkenin değerlerini otomatik olarak önbelleğe almamak amacıyla ortaya çıktı. Derleyiciye bu değişkenin değerini önbelleğe almamasını söyleyecektir. Böylece, verilen volatiledeğişkenin değerini her karşılaştığında ana bellekten almak için kod üretecektir . Bu mekanizma kullanılır çünkü değer herhangi bir zamanda işletim sistemi veya herhangi bir kesinti tarafından değiştirilebilir. Bu nedenle, kullanmak volatileher seferinde değere yeniden erişmemize yardımcı olacaktır.


Varlığa dönüşmek? "Uçucu" başlangıçta C ++ 'dan ödünç alınmadı mı? Hatırlıyorum ...
sentaksör

Bu uçucu değil - aynı zamanda uçucu olarak belirtilirse yeniden sıralamayı yasaklar ..
FaceBro

4
@FaceBro: volatileDerleyicilerin kodu optimize etmesini mümkün kılmakla birlikte , programcıların bu tür optimizasyonlar olmadan elde edilebilecek anlambilim elde etmelerine izin vermekti. Standardın yazarları, kalite uygulamalarının hedef platformları ve uygulama alanları göz önüne alındığında yararlı olan her şeyi destekleyeceğini umuyorlardı ve derleyici yazarlarının Standarda uygun ve% 100 olmayan en düşük kalitede anlambilim sunmaya çalışmasını beklemiyorlardı. aptal (Standart yazarlarının
mantıkta

1
... bir uygulamanın, aslında herhangi bir amaca uygun olacak kadar iyi kalitede olmadan uyum sağlamasının mümkün olduğunu, ancak bunu önlemenin gerekli olmadığını düşünüyorlardı).
supercat

1
@syntaxerror C, C ++ 'dan on yıldan daha eski olduğunda (hem ilk sürümlerde hem de ilk standartlarda) C ++' dan nasıl ödünç alınabilir?
phuclv

178

Bunun bir başka kullanımı volatilesinyal işleyicilerdir. Böyle bir kodunuz varsa:

int quit = 0;
while (!quit)
{
    /* very small loop which is completely visible to the compiler */
}

Derleyicinin, döngü gövdesinin quitdeğişkene dokunmadığını ve döngüyü bir while (true)döngüye dönüştürmediğini fark etmesine izin verilir . Bile quitdeğişken için sinyal işleyici ayarlanır SIGINTve SIGTERM; derleyicinin bunu bilmesinin bir yolu yoktur.

Ancak, quitdeğişken bildirilirse volatile, derleyici her seferinde onu yüklemek zorunda kalır, çünkü başka bir yerde değiştirilebilir. Bu durumda tam olarak istediğiniz şey budur.


"derleyici her seferinde yüklemeye zorlanır, derleyici belirli bir değişkeni optimize etmeye karar verdiğinde ve değişkeni çalışma zamanında belirli olmayan değişkenlerin bellekte olmayan CPU kayıtlarına yüklendiğinde değişken olarak bildirmediğimizde olduğu gibi ?
Amit Singh Tomar

1
@AmitSinghTomar Bu ne demek oluyor demektir: Kod değeri her kontrol ettiğinde yeniden yüklenir. Aksi takdirde, derleyicinin değişkeni referans almayan işlevlerin değiştiremediğini varsaymasına izin verilir, bu nedenle CesarB'nin yukarıdaki döngünün ayarlanmamış olması amaçlandığında quit, derleyicinin sabit bir döngü haline getirebileceğini varsayarsak, quityinelemeler arasında değiştirilmenin bir yolu olmadığını . Not: Bu, gerçek threadsafe programlama için iyi bir alternatif değildir.
underscore_d

quit global bir değişkense, derleyici while döngüsünü optimize etmez, değil mi?
Pierre G.

2
@PierreG. Hayır, derleyici aksi belirtilmedikçe her zaman kodun tek iş parçacıklı olduğunu varsayabilir. Yani, volatileya da başka işaretçilerin yokluğunda , döngü dışında hiçbir şeyin döngüye girdiğinde, değişkeni global bir değişken olsa bile değiştirmediğini varsayar.
CesarB

1
@PierreG. Evet, derleme örneğin denemek extern int global; void fn(void) { while (global != 0) { } }ile gcc -O3 -Sve elde edilen montaj dosyasına bakmak, öyle benim makinede movl global(%rip), %eax; testl %eax, %eax; je .L1; .L4: jmp .L4, yani küresel sıfır değilse sonsuz bir döngü. Ardından eklemeyi deneyin volatileve farkı görün.
CesarB

60

volatilederleyiciye değişkeninizin ona erişen koddan başka yollarla değiştirilebileceğini söyler. örneğin, bir G / Ç eşlemeli bellek konumu olabilir. Bu gibi durumlarda bu belirtilmezse, bazı değişken erişimler optimize edilebilir, örneğin içerikleri bir kayıt defterinde tutulabilir ve bellek konumu tekrar okunmaz.


30

Bu makaleye bakınız: Andrei Alexandrescu, " geçici - Çok İş Parçacıklı Programcı'nın En İyi Arkadaşı "

Uçucu kelime belli asenkron olayların huzurunda yanlış kod kılabilir derleyici optimizasyonlar engellemek üzere geliştirildi. Örneğin, ilkel bir değişkeni geçici olarak bildirirseniz, derleyicinin bunu bir kayıt defterinde önbelleğe almasına izin verilmez - bu değişkenin birden çok iş parçacığı arasında paylaşılması durumunda felaket olacak ortak bir optimizasyon. Dolayısıyla genel kural, birden çok iş parçacığı arasında paylaşılması gereken ilkel tür değişkenleriniz varsa, bu değişkenleri geçici olarak bildirir. Ama aslında bu anahtar kelime ile çok daha fazlasını yapabilirsiniz: iş parçacığı güvenli olmayan kodu yakalamak için kullanabilirsiniz ve bunu derleme zamanında yapabilirsiniz. Bu makale nasıl yapıldığını göstermektedir; çözüm, kodun kritik bölümlerini serileştirmeyi de kolaylaştıran basit bir akıllı işaretçi içerir.

Makale için geçerlidir Cve C++.

Ayrıca Scott Meyers ve Andrei Alexandrescu'nun " C ++ ve Çift Kontrollü Kilitlemenin Tehlikeleri " makalesine bakın :

Bu nedenle, bazı bellek konumlarıyla (örn. Bellek eşlemeli bağlantı noktaları veya ISR'ler [Kesme Servisi Rutinleri] tarafından başvurulan bellek) ilgilenirken, bazı optimizasyonların askıya alınması gerekir. bu tür konumlar için özel muamele belirtmek için uçucu mevcuttur, özellikle: (1) uçucu değişkenin içeriği "kararsız" dır (derleyici tarafından bilinmeyen bir şekilde değişebilir), (2) uçucu verilere yapılan tüm yazılar "gözlemlenebilir" olduğundan dini olarak yürütülmelidir ve (3) uçucu veriler üzerindeki tüm işlemler kaynak kodunda göründükleri sırayla yürütülür. İlk iki kural düzgün okuma ve yazma sağlar. Sonuncusu, giriş ve çıkışı karıştıran I / O protokollerinin uygulanmasına izin verir. Bu gayri resmi olarak C ve C ++ 'nin uçucu garantisidir.


Standart, değer hiç kullanılmadığında bir okumanın 'gözlemlenebilir davranış' olarak değerlendirilip değerlendirilmeyeceğini belirtir mi? Benim izlenimim öyle olmalı, ama başka bir yerde olduğunu iddia ettiğimde birisi bana alıntı yapmak için meydan okudu. Bana öyle geliyor ki, uçucu bir değişkenin okunmasının akla yatkın bir etkisi olabileceği herhangi bir platformda, bir derleyicinin belirtilen her okumayı tam olarak bir kez gerçekleştiren kod üretmesi gerekir; bu gereklilik olmadan, öngörülebilir bir okuma dizisi oluşturan kod yazmak zor olacaktır.
supercat

@supercat: İlk makaleye göre, "Değişken üzerinde uçucu değiştirici kullanırsanız, derleyici bu değişkeni kayıtlarda önbelleğe almaz - her erişim o değişkenin gerçek bellek konumuna çarpar." Ayrıca, c99 standardının §6.7.3.6 bölümünde şöyle diyor: "Uçucu nitelikte bir türe sahip bir nesne, uygulama tarafından bilinmeyen şekillerde değiştirilebilir veya başka bilinmeyen yan etkilere sahip olabilir." Ayrıca, uçucu değişkenlerin yazmaçlarda önbelleğe alınamayabileceği ve tüm okuma ve yazma işlemlerinin sıra noktalarına göre gerçekte gözlemlenebilecek şekilde yürütülmesi gerektiği anlamına gelir.
Robert S. Barnes

İkinci makale gerçekten de okumanın yan etkiler olduğunu açıkça belirtmektedir. Birincisi, okumaların sırayla gerçekleştirilemediğini, ancak bunların tamamen ortadan kaldırılma olasılığını engellemediğini göstermektedir.
Supercat

"derleyici bir kayıtta önbelleğe izin verilmemektedir" - herhangi salt değiştirme-yazma yüzden çoğu RISC arcitectures, kayıt-makineleri ae sahip bir kayıtlarında nesne önbelleğe. volatileatomisiteyi garanti etmez.
bu site için çok dürüst

1
@Olaf: Kayıt defterine bir şey yüklemek önbellekleme ile aynı şey değildir. Önbelleğe alma, yüklerin veya depoların sayısını veya zamanlamasını etkiler.
supercat

28

Basit açıklamam:

Bazı senaryolarda, mantık veya koda bağlı olarak, derleyici değişmediğini düşündüğü değişkenlerin optimizasyonunu yapar. volatileAnahtar kelime önler değişken optimize edilen.

Örneğin:

bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
    // execute logic for the scenario where the USB isn't connected 
}

Yukarıdaki koddan, derleyici usb_interface_flag0 olarak tanımlanabilir ve while döngüsünde sonsuza kadar sıfır olacağını düşünebilir . Optimizasyondan sonra derleyici her while(true)zaman olduğu gibi sonsuz döngüye neden olur.

Bu tür senaryolardan kaçınmak için, bayrağı geçici olarak beyan ederiz, derleyiciye bu değerin harici bir arayüz veya başka bir program modülü tarafından değiştirilebileceğini söylüyoruz, lütfen optimize etmeyin. Bu, uçucu için kullanım durumudur.


19

Uçucu için marjinal bir kullanım aşağıdaki gibidir. Bir fonksiyonun sayısal türevini hesaplamak istediğinizi varsayalım f:

double der_f(double x)
{
    static const double h = 1e-3;
    return (f(x + h) - f(x)) / h;
}

Sorun, x+h-xgenel olarak h, yuvarlama hataları nedeniyle eşit olmamasıdır . Bir düşünün: çok yakın sayıları özetlediğinizde, türevin hesaplamasını mahvedebilecek çok sayıda önemli basamak kaybedersiniz (düşünün 1.00001 - 1). Olası bir geçici çözüm olabilir

double der_f2(double x)
{
    static const double h = 1e-3;
    double hh = x + h - x;
    return (f(x + hh) - f(x)) / hh;
}

ancak platformunuza ve derleyici anahtarlarınıza bağlı olarak, bu işlevin ikinci satırı agresif bir şekilde optimize edilmiş bir derleyici tarafından silinebilir. Onun yerine sen yaz

    volatile double hh = x + h;
    hh -= x;

derleyiciyi hh içeren bellek konumunu okumaya zorlayarak nihai bir optimizasyon fırsatını kaybeder.


Türev formülde hveya hhtürev formülde kullanma arasındaki fark nedir ? Tüm hhhesaplanan son formül fark ile, ilki gibi kullanır. Belki de olmalı (f(x+h) - f(x))/hh?
Sergey Zhukov

2
Arasındaki fark hve hholmasıdır hhameliyatla ikisinin bir negatif güç kesilir x + h - x. Bu durumda x + hhve ile xtam olarak farklılık gösterir hh. Formülünüzü de alabilirsiniz, aynı sonucu verecektir, çünkü x + hve x + hheşittir (burada önemli olan paydadır).
Alexandre C.

3
Bunu yazmanın daha okunabilir bir yolu olmaz x1=x+h; d = (f(x1)-f(x))/(x1-x)mı? uçucu kullanmadan.
Sergey Zhukov

Bir derleyicinin işlevin ikinci satırını silebileceği herhangi bir başvuru var mı?
CoffeeTableEspresso

@CoffeeTableEspresso: Hayır, üzgünüm. Kayan nokta hakkında ne kadar çok bilgi sahibi olursam, derleyicinin sadece açık bir şekilde -ffast-mathveya eşdeğer olarak söylendiğinde optimize etmesine izin verildiğine inanıyorum .
Alexandre C.

11

İki kullanım alanı vardır. Bunlar özellikle gömülü gelişimde daha sık kullanılır.

  1. Derleyici, geçici anahtar kelimeyle tanımlanan değişkenleri kullanan işlevleri optimize etmez

  2. Uçucu, RAM, ROM, vs.'deki tam bellek konumlarına erişmek için kullanılır ... Bu, bellek eşlemeli aygıtları kontrol etmek, CPU kayıtlarına erişmek ve belirli bellek konumlarını bulmak için daha sık kullanılır.

Montaj listesindeki örneklere bakın. Re: Gömülü Geliştirme C "uçucu" Anahtar Kelime Kullanımı


"Derleyici, değişken anahtar sözcükle tanımlanan değişkenleri kullanan işlevleri optimize etmez" - bu oldukça yanlıştır.
bu site için çok dürüst

10

Uçucu ayrıca, derleyiciyi belirli bir kod dizisini optimize etmemeye zorlamak istediğinizde de yararlıdır (örneğin, bir mikro karşılaştırma ölçütü yazmak için).


10

Uçucuların önemli olduğu başka bir senaryodan bahsedeceğim.

Daha hızlı G / Ç için bir dosyayı bellekle eşleştirdiğinizi ve bu dosyanın sahne arkasında değişebileceğini varsayalım (örneğin, dosya yerel sabit sürücünüzde değil, bunun yerine başka bir bilgisayar tarafından ağ üzerinden sunulur).

Bellek eşlemeli dosyanın verilerine uçucu olmayan nesnelere (kaynak kodu düzeyinde) işaretçilerle erişirseniz, derleyici tarafından oluşturulan kod siz farkında olmadan aynı verileri birden çok kez alabilir.

Bu veriler değişirse, programınız verilerin iki veya daha fazla farklı sürümünü kullanabilir ve tutarsız bir duruma geçebilir. Bu, yalnızca programın mantıksal olarak yanlış davranışına değil, aynı zamanda güvenilmeyen dosyaları veya güvenilmeyen konumlardaki dosyaları işlerse içindeki güvenlik açıklarına neden olabilir.

Güvenliği önemsiyorsanız ve yapmalısınızsa, bu dikkate alınması gereken önemli bir senaryodur.


7

geçici olarak, depolamanın her zaman değişebileceği ve değiştirilebileceği, ancak kullanıcı programının kontrolü dışında bir şey olduğu anlamına gelir. Bu, değişkene başvurursanız, programın her zaman fiziksel adresi (yani eşlenmiş bir giriş fifo) kontrol etmesi ve önbelleğe alınmış bir şekilde kullanmaması gerektiği anlamına gelir.


Hiçbir derleyici, "RAM'deki fiziksel adres" veya "önbelleği atla" anlamına gelen geçici değildir.
curiousguy


5

Bence, çok fazla beklememelisiniz volatile. Açıklamak için Nils Pipenbrinck'in yüksek oyu alan cevabındaki örneğe bakın .

Onun örneği için uygun olmadığını söyleyebilirim volatile. volatileyalnızca şu amaçlarla kullanılır: derleyicinin yararlı ve istenen optimizasyonlar yapmasını önlemek . İş parçacığı güvenli, atomik erişim ve hatta bellek sırası hakkında hiçbir şey değildir.

Bu örnekte:

    void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

gadget->data = dataönce gadget->command = commandyalnızca derleyici tarafından derlenmiş kod garantilidir. Çalışma zamanında, işlemci hala işlemci mimarisine ilişkin verileri ve komut atamasını yeniden sıralar. Donanım yanlış verileri alabilir (gadget'ın donanım G / Ç ile eşleştirildiğini varsayalım). Veri ve komut ataması arasında bellek engeli gerekir.


2
Uçucunun, derleyicinin normalde yararlı ve arzu edilen optimizasyonları yapmasını önlemek için kullanıldığını söyleyebilirim . Yazıldığı gibi volatile, hiçbir sebepten dolayı performansı düşürüyor gibi görünüyor . Bunun yeterli olup olmadığına gelince, bu, programlayıcının derleyiciden daha fazla bilgi sahibi olabileceği diğer yönlerine bağlı olacaktır. Öte yandan, bir işlemci belirli bir adrese yazma talimatının CPU önbelleğini temizleyeceğini garanti ederse, ancak bir derleyici CPU'nun hiçbir şey bilmediği, kayıt önbelleğe alınmış değişkenleri yıkamanın hiçbir yolunu sağlamazsa, önbelleği yıkamak işe yaramaz.
supercat

5

Dennis Ritchie tarafından tasarlanan dilde, adresi alınmamış otomatik nesneler dışındaki herhangi bir nesneye her erişim, nesnenin adresini hesaplamış gibi davranır ve daha sonra bu adresteki depolamayı okur veya yazar. Bu, dili çok güçlü, ancak oldukça sınırlı optimizasyon fırsatları haline getirdi.

Bir derleyiciyi belirli bir nesnenin garip yollarla değiştirilmeyeceğini varsaymaya davet edecek bir niteleyici eklemek mümkün olsa da, böyle bir varsayım C programlarındaki nesnelerin büyük çoğunluğu için uygun olacaktır ve bu varsayımın uygun olacağı tüm nesnelere bir niteleyici eklemek pratik değildir. Öte yandan, bazı programların böyle bir varsayımın olmayacağı bazı nesneleri kullanması gerekir. Bu sorunu çözmek için Standart, derleyicilerin beyan edilmeyen nesnelerin volatiledeğerlerini derleyicinin denetiminin dışında veya makul bir derleyicinin anlayışının dışında bir şekilde gözlemlemeyeceğini veya değiştirmeyeceğini varsayabileceğini söylüyor .

Çeşitli platformlar, bir derleyicinin kontrolü dışında nesnelerin gözlemlenebileceği veya değiştirilebileceği farklı yollara sahip olabileceğinden, bu platformlar için kaliteli derleyicilerin volatilesemantiklerin tam olarak işlenmesinde farklılık göstermesi uygundur . Ne yazık ki, Standart, bir platformda düşük düzeyli programlama için tasarlanan kalite derleyicilerinin volatile, belirli bir okuma / yazma işleminin bu platformdaki tüm ilgili etkilerini tanıyacak şekilde işlemesi gerektiğini önermediğinden, birçok derleyici yapmakta yetersiz kalmaktadır böylece arka plan G / Ç gibi şeyleri verimli ancak derleyici "optimizasyonları" ile kırılamayacak bir şekilde işlemeyi zorlaştıracak şekillerde.


5

Basit bir ifadeyle, derleyiciye belirli bir değişken üzerinde herhangi bir optimizasyon yapmamasını söyler. Aygıt kaydına eşlenen değişkenler aygıt tarafından dolaylı olarak değiştirilir. Bu durumda, uçucu kullanılmalıdır.


1
Bu cevapta daha önce bahsedilmeyen yeni bir şey var mı?
slfan

3

Bir uçucu derlenmiş kodun dışından değiştirilebilir (örneğin, bir program uçucu bir değişkeni bellek eşlemeli bir kayıtla eşleyebilir.) Derleyici, uçucu bir değişkeni işleyen koda belirli optimizasyonlar uygulamaz - örneğin, ' t Belleğe yazmadan kayıt defterine yükleme. Bu, donanım kayıtları ile uğraşırken önemlidir.


0

Burada birçok kişi tarafından haklı olarak önerildiği gibi, uçucu anahtar kelimenin popüler kullanımı, değişken değişkenin optimizasyonunu atlamaktır.

Akla gelen ve uçucu hakkında okuduktan sonra bahsetmeye değer en iyi avantaj - a durumunda değişkenin geri alınmasını önlemektir longjmp. Yerel olmayan bir atlama.

Ne anlama geliyor?

Bu , yığın çözme işleminden sonra önceki değerin bazı yığın çerçevelerine dönmek için son değerin korunacağı anlamına gelir ; tipik olarak bazı hatalı senaryolar durumunda.

Bu sorunun kapsamı dışında olacağı için, setjmp/longjmpburada ayrıntılara girmiyorum , ama bu konuda okumaya değer; ve volatilite özelliğinin son değeri korumak için nasıl kullanılabileceği.


-2

derleyicinin değişken değerlerini otomatik olarak değiştirmesine izin vermez. değişken bir değişken dinamik kullanım içindir.

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.