volatile
C'de neden gereklidir? Ne için kullanılır? Ne yapacak?
volatile
C'de neden gereklidir? Ne için kullanılır? Ne yapacak?
Yanıtlar:
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;
}
volatile
C 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 volatile
değ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 volatile
her seferinde değere yeniden erişmemize yardımcı olacaktır.
volatile
Derleyicilerin 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
Bunun bir başka kullanımı volatile
sinyal 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 quit
değişkene dokunmadığını ve döngüyü bir while (true)
döngüye dönüştürmediğini fark etmesine izin verilir . Bile quit
değişken için sinyal işleyici ayarlanır SIGINT
ve SIGTERM
; derleyicinin bunu bilmesinin bir yolu yoktur.
Ancak, quit
değ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.
quit
, derleyicinin sabit bir döngü haline getirebileceğini varsayarsak, quit
yinelemeler arasında değiştirilmenin bir yolu olmadığını . Not: Bu, gerçek threadsafe programlama için iyi bir alternatif değildir.
volatile
ya 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.
extern int global; void fn(void) { while (global != 0) { } }
ile gcc -O3 -S
ve 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 volatile
ve farkı görün.
volatile
derleyiciye 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.
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 C
ve 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.
volatile
atomisiteyi garanti etmez.
Basit açıklamam:
Bazı senaryolarda, mantık veya koda bağlı olarak, derleyici değişmediğini düşündüğü değişkenlerin optimizasyonunu yapar. volatile
Anahtar 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_flag
0 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.
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-x
genel 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.
h
veya hh
türev formülde kullanma arasındaki fark nedir ? Tüm hh
hesaplanan son formül fark ile, ilki gibi kullanır. Belki de olmalı (f(x+h) - f(x))/hh
?
h
ve hh
olmasıdır hh
ameliyatla ikisinin bir negatif güç kesilir x + h - x
. Bu durumda x + hh
ve ile x
tam olarak farklılık gösterir hh
. Formülünüzü de alabilirsiniz, aynı sonucu verecektir, çünkü x + h
ve x + hh
eşittir (burada önemli olan paydadır).
x1=x+h; d = (f(x1)-f(x))/(x1-x)
mı? uçucu kullanmadan.
-ffast-math
veya eşdeğer olarak söylendiğinde optimize etmesine izin verildiğine inanıyorum .
İki kullanım alanı vardır. Bunlar özellikle gömülü gelişimde daha sık kullanılır.
Derleyici, geçici anahtar kelimeyle tanımlanan değişkenleri kullanan işlevleri optimize etmez
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ı
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).
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.
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.
Wiki hakkında her şeyi söylüyor volatile
:
Ve Linux çekirdeğinin dokümanı da aşağıdakiler hakkında mükemmel bir gösterim yapar volatile
:
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
. volatile
yalnı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 = command
yalnı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.
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.
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 volatile
değ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 volatile
semantiklerin 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.
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.
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.
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/longjmp
burada 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.