20 Temmuz 2020 Pazartesi

volatile qualifier - Modern C++ ile Artık Sadece Donanıma Yazma, Donanımdan Okuma İçindir

Giriş
- volatile sadece donanımdaki register'lara erişirken (yazmak ve okuma) kullanılır.
- volatile değişkenlere erişim optimize edilmez
- volatile değişkenlerin Execution Order'ı  değiştirilmez

Thread'ler Arası Kullanım İçin Değildir
Açıklaması şöyle
For a multi-threaded system using C++11 or newer, volatile is not a mechanism for communicating between threads.
volatile bir qualifier'dır
volatile aynı const gibi bir qualifier'dır. Elimizde şöyle bir kod olsun.
volatile int i{100};
int j{200};
std::cout << std::min(i, j);
Bu kod derlenmez, çıktı olarak şunu alırız.
error: no matching function for call to ‘min(volatile int&, int&)’
Çünkü std::min() her iki parametresinin de aynı tip olmasını ister. Şöyle yaparız.
std::min<volatile int>(i, j)
volatile qualifier Kaldırılamaz
Açıklaması şöyle.
6.7.3:5 If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.
Örnek
volatile array normal array'e cast edilemez. Şu kod undefined behavior'a sebep olur.
storeArray((unsigned char *) buffer);
volatile Ne Zaman Kullanılır
Açıklaması şöyle. Yani volatile donanıma yazılırken ve okunurken kullanılır. Esas amacı derleyicinin kodu optimize etmemesi  ve Execution Order'ı değiştirmemesi.
representing hardware registers (or memory-mapped IO) as variables - Even if the register will never be read, the compiler must not just skip the write operation thinking "Stupid programmer. Tries to store a value in a variable which he will never ever read back. He won't even notice if we omit the write." On the other hand, even if the program never writes a value to the variable, its value may still be changed by hardware.
Bir başka açıklama şöyle.
Hardware registers are always volatile qualified and the compiler is not allowed to optimize code containing volatile access. So if you write to them 8 times, then 8 writes is what you get. This is of course much slower than 1 write.
Örnek - optimize edilememesi
Elimizde şöyle bir kod olsun. Bu kodun assembly çıktısına bakarsak funv() metodundaki volatile değişkene iki write işleminin de aynı değeri yazmasına rağmen koda girdiğini görürüz. 
Ancak fun() metoduda volatile olmayan değişkene sadece bir tane write işlemin olduğunu görürüz. Yani volatile olmayan değişken optimize ediliyor ve koda girmiyor.
#define MYFIFOV (*((volatile unsigned char *)0x1000000))
#define MYFIFO (*((unsigned char *)0x1000000))

void funv (void)
{
  MYFIFOV=0;
  MYFIFOV=0;
}
void fun (void)
{
  MYFIFO=0;
  MYFIFO=0;
}
00000000 <funv>:
   0:   e3a03401    mov r3, #16777216   ; 0x1000000
   4:   e3a02000    mov r2, #0
   8:   e5c32000    strb    r2, [r3]
   c:   e5c32000    strb    r2, [r3]
  10:   e12fff1e    bx  lr

00000014 <fun>:
  14:   e3a03401    mov r3, #16777216   ; 0x1000000
  18:   e3a02000    mov r2, #0
  1c:   e5c32000    strb    r2, [r3]
  20:   e12fff1e    bx  lr
Açıklaması şöyle
strb means store byte. Without the volatile one of the writes was optimized out. So yes without volatile, writes can be optimized out. How and when the compiler decides to do it can vary. But assume it can happen and as a result cause you problems.
Örnek - optimize edilememesi
Elimizde şöyle bir kod olsun.
int fn() {
  volatile int x = 0;
  return x;
}
Volatile değişken kullanılmasa bile optimize edilip silinemez. Yani kod şu hale gelemez.
int fn() {
  return 0;
}
volatile Değişken Execution Order'ı Garanti Eder
Örnek
volatile int i;
volatile int j;
int a;

...

i = 1;
a = 99;
j = 2;
Açıklaması şöyle.
is guaranteed to first assign 1 to i and then assign 2 to j. However, it is not guaranteed that a will be assigned in between; the compiler may do that assignment before or after the code snippet, basically at any time up to the first (visible) read of a.
Volatile Değişken Order of Evaluation Varsa Erişim Sırasını Garanti Etmez
Bu konu aslında Order of Evaluation başlığı altına giriyor ancak bazen Sequence olarak ta anılıyor.

Şöyle bir örnek olsun. Bu örnekte a'nın değeri değişmediği için sorun yok.
#include <iostream>
volatile int a;
int main() {
  std::cout << (a + a);
}
Ancak şöyle bir kod olsaydı sorun olurdu.
std::cout << a * (a+1);
Açıklaması şöyle
The issue here is not the missing initializer of the variable a - it will implicitly be initialized to 0 here. But the issue is the access to a twice without sequencing between the accesses. According to §1.9¶12, accesses of volatile glvalues are side-effects and according to §1.9¶15 these two unsequenced side-effects on the same scalar object result in undefined behavior.
volatile Değişken Atomic Access Sağlamaz

Açıklaması şöyle.
volatile does not provide atomic access to multi-word variables. For those cases, you will need to provide mutual exclusion by other means, in addition to using volatile.
volatile array
Elimizde şöyle bir kod olsun.
#include <type_traits>

template<typename T>
inline void DoStuff(T val) {
  static_assert(!std::is_volatile<T>::value, "No volatile types plz");
    //...
}

int main() {
  volatile char sometext[261];
  DoStuff(sometext);
}
T tipi array decay olduğı için "volatile char *" tipi olarak görülür.Yani
It's a non-volatile pointer to a volatile char array.
volatile Qualifier'a Sahip Nesne
Açıklaması şöyle
The volatile qualifier makes the implicit this parameter be treated as a pointer to a volatile object.

Essentially, this means that the semantics of volatile objects will be applied when accessing the data member(s) of the object. Any read of x cannot be optimized away, even if the compiler can prove there is no recent write after the last read.
Örnek
Elimizde şöyle bir kod olsun
volatile struct { int foo; int bar; } data;
data.foo = 1;
data.bar = 2;
data.foo = 3;
data.bar = 4;
Bu atama sırasını yani Execution Order'ı derleyici değiştiremez. Açıklaması şöyle
Since data has volatile-qualified type, so do data.bar and data.foo. Thus you are performing two assignments to volatile int objects.
Örnek
Volatile nesne, bir başka volatile olan veya olmayan nesneye çevrilemez. Çünkü çoğu nesne için constructor şöyledir. Volatile qualifier kullanan bir constructor olmadığı için çevrim gerçekleşmez.
X::X(const X&)
Elimizde şöyle bir kod olsun. Çıktı olarak 1 1 0 0 alırız
#include <iostream>
#include <type_traits>

struct type {};

int main(int argc, char* argv[]) {
  std::cout << std::is_convertible_v<volatile int, int> << " ";
  std::cout << std::is_convertible_v<volatile int, volatile int> << " ";
  std::cout << std::is_convertible_v<volatile type, type> << " ";
  std::cout << std::is_convertible_v<volatile type, volatile type> << std::endl;
  return 0;
}
Örnek
volatile olmayan  nesnenin volatile olan metodu çağrılabilir. Açıklaması şöyle
If the object isn't volatile the body of func is still correct, albeit not as optimized as it can be. So you can call it just fine.
Şöyle yaparız.
class A 
{
private:
  int x;
public:
  void func(int a) volatile //volatile function
  {
    x = a;
    cout<<x<<endl;
  }
};

A a1; // non volatile object
a1.func(10);
Örnek
Volatile olan nesnenin volatile olmayan metodu çağrılamaz. Şu kod derlenmez.
class A 
{
private:
  int x;
public:
  void func(int a) // non-volatile member function
  {
    x = a;
    cout<<x<<endl;
  }
};

volatile A a1;   // volatile object
a1.func(10);     // fail

Hiç yorum yok:

Yorum Gönder