volatile

C++ 2019. 12. 25. 09:49
반응형

volatile (C++)

 

volatile (C++)

volatile (C++) In this article --> A type qualifier that you can use to declare that an object can be modified in the program by the hardware. Syntax volatile declarator ; You can use the /volatile compiler switch to modify how the compiler interprets this

docs.microsoft.com

하드웨어의 프로그램에서 객체를 수정될 수 있음을 선언하는데 사용할 수 있는 유형의 한정자입니다.

-> 간단히 말하면 한정자로 꾸며진 변수가 후에 값이 변동될 수 있음을 CPU에게 알리는 것이다.

 

 

더 참고_

 

volatile (computer programming) - Wikipedia

In computer programming, particularly in the C, C++, C#, and Java programming languages, the volatile keyword indicates that a value may change between different accesses, even if it does not appear to be modified. This keyword prevents an optimizing compi

en.wikipedia.org

더보기

volatile (computer programming)

From Wikipedia, the free encyclopedia

Jump to navigationJump to search

 

In computer programming, particularly in the C, C++, C#, and Java programming languages, the volatile keyword indicates that a value may change between different accesses, even if it does not appear to be modified. This keyword prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes. Volatile values primarily arise in hardware access (memory-mapped I/O), where reading from or writing to memory is used to communicate with peripheral devices, and in threading, where a different thread may have modified a value.

Despite being a common keyword, the behavior of volatile differs significantly between programming languages, and is easily misunderstood. In C and C++, it is a type qualifier, like const, and is a property of the type. Furthermore, in C and C++ it does not work in most threading scenarios, and that use is discouraged. In Java and C#, it is a property of a variable and indicates that the object to which the variable is bound may mutate, and is specifically intended for threading. In the D programming language, there is a separate keyword shared for the threading usage, but no volatile keyword exists.

Contents

In C and C++[edit]

In C, and consequently C++, the volatile keyword was intended to[1]

  • allow access to memory-mapped I/O devices
  • allow uses of variables between setjmp and longjmp
  • allow uses of sig_atomic_t variables in signal handlers.

Operations on volatile variables are not atomic, nor do they establish a proper happens-before relationship for threading. This is specified in the relevant standards (C, C++, POSIX, WIN32),[1] and volatile variables are not threadsafe in the vast majority of current implementations. Thus, the usage of volatile keyword as a portable synchronization mechanism is discouraged by many C/C++ groups.[2][3][4]

Example of memory-mapped I/O in C[edit]

In this example, the code sets the value stored in foo to 0. It then starts to poll that value repeatedly until it changes to 255:

static int foo; void bar(void) { foo = 0; while (foo != 255) ; }

An optimizing compiler will notice that no other code can possibly change the value stored in foo, and will assume that it will remain equal to 0 at all times. The compiler will therefore replace the function body with an infinite loop similar to this:

void bar_optimized(void) { foo = 0; while (true) ; }

However, foo might represent a location that can be changed by other elements of the computer system at any time, such as a hardware register of a device connected to the CPU. The above code would never detect such a change; without the volatile keyword, the compiler assumes that the current program is the only part of the system that could change the value (which is by far the most common situation).

To prevent the compiler from optimizing code as above, the volatile keyword is used:

static volatile int foo; void bar (void) { foo = 0; while (foo != 255) ; }

With this modification the loop condition will not be optimized away, and the system will detect the change when it occurs.

Generally, there are memory barrier operations available on platforms (which are exposed in C++11) that should be preferred instead of volatile as they allow the compiler to perform better optimization and more importantly they guarantee correct behaviour in multi-threaded scenarios; neither the C specification (before C11) nor the C++ specification (before C++11) specifies a multi-threaded memory model, so volatile may not behave deterministically across OSes/compilers/CPUs).[5]

Optimization comparison in C[edit]

The following C programs, and accompanying assemblies, demonstrate how the volatile keyword affects the compiler's output. The compiler in this case was GCC.

While observing the assembly code, it is clearly visible that the code generated with volatile objects is more verbose, making it longer so the nature of volatile objects can be fulfilled. The volatile keyword prevents the compiler from performing optimization on code involving volatile objects, thus ensuring that each volatile variable assignment and read has a corresponding memory access. Without the volatile keyword, the compiler knows a variable does not need to be reread from memory at each use, because there should not be any writes to its memory location from any other thread or process.

showAssembly comparison

C++11[edit]

According to the C++11 ISO Standard, the volatile keyword is only meant for use for hardware access; do not use it for inter-thread communication. For inter-thread communication, the standard library provides std::atomic<T> templates.[6]

In Java[edit]

The Java programming language also has the volatile keyword, but it is used for a somewhat different purpose. When applied to a field, the Java qualifier volatile provides the following guarantees:

  • In all versions of Java, there is a global ordering on reads and writes of all volatile variables (this global ordering on volatiles is a partial order over the larger synchronization order (which is a total order over all synchronization actions)). This implies that every thread accessing a volatile field will read its current value before continuing, instead of (potentially) using a cached value. (However, there is no guarantee about the relative ordering of volatile reads and writes with regular reads and writes, meaning that it's generally not a useful threading construct.)
  • In Java 5 or later, volatile reads and writes establish a happens-before relationship, much like acquiring and releasing a mutex.[7]

Using volatile may be faster than a lock, but it will not work in some situations.[citation needed] The range of situations in which volatile is effective was expanded in Java 5; in particular, double-checked locking now works correctly.[8]

In C#[edit]

In C#, volatile ensures that code accessing the field is not subject to some thread-unsafe optimizations that may be performed by the compiler, the CLR, or by hardware. Only the following types can be marked volatile: all reference types, Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Char, and all enumerated types with an underlying type of Byte, SByte, Int16, UInt16, Int32, or UInt32.[9] (This excludes value structs, as well as the primitive types Double, Int64, UInt64 and Decimal.)

Basically volatile is a shorthand for calling Thread.VolatileRead and Thread.VolatileWrite. These methods are special. In effect, these methods disable some optimizations usually performed by the C# compiler, the JIT compiler, and the CPU itself. The methods work as follows:[10]

  • The Thread.VolatileWrite method forces the value in address to be written to at the point of the call. In addition, any earlier program-order loads and stores must occur before the call to VolatileWrite.
  • The Thread.VolatileRead method forces the value in address to be read from at the point of the call. In addition, any later program-order loads and stores must occur after the call to VolatileRead.
  • The Thread.MemoryBarrier method does not access memory but it forces any earlier program order loads and stores to be completed before the call to MemoryBarrier. It also forces any later program-order loads and stores to be completed after the call to MemoryBarrier. MemoryBarrier is much less useful than the other two methods.[citation needed]

In Fortran[edit]

VOLATILE is part of the Fortran 2003 standard,[11] although earlier version supported it as an extension. Making all variables volatile in a function is also useful finding aliasing related bugs.

integer, volatile :: i ! When not defined volatile the following two lines of code are identical write(*,*) i**2 ! Loads the variable i once from memory and multiplies that value times itself write(*,*) i*i ! Loads the variable i twice from memory and multiplies those values

By always "drilling down" to memory of a VOLATILE, the Fortran compiler is precluded from reordering reads or writes to volatiles. This makes visible to other threads actions done in this thread, and vice versa.[12]

Use of VOLATILE reduces and can even prevent optimization.[13]

References[edit]

  1. ^ Jump up to:a b "Publication on C++ standards committee".
  2. ^ "Volatile Keyword In Visual C++". Microsoft MSDN.
  3. ^ "Linux Kernel Documentation – Why the "volatile" type class should not be used". kernel.org.
  4. ^ "C++ and the Perils of Double-Checked Locking" (PDF). DDJ.
  5. ^ "Linux: Volatile Superstition". kerneltrap.org. Retrieved Jan 9, 2011.
  6. ^ "volatile (C++)". Microsoft MSDN.
  7. ^ Section 17.4.4: Synchronization Order "The Java® Language Specification, Java SE 7 Edition". Oracle Corporation. 2013. Retrieved 2013-05-12.
  8. ^ Neil Coffey. "Double-checked Locking (DCL) and how to fix it". Javamex. Retrieved 2009-09-19.
  9. ^ Richter, Jeffrey (February 11, 2010). "Chapter 7: Constants and Fields". CLR Via C#. Microsoft Press. p. 183. ISBN 0-7356-2704-5.
  10. ^ Richter, Jeffrey (February 11, 2010). "Chapter 28: Primitive Thread Synchronization Constructs". CLR Via C#. Microsoft Press. pp. 797–803. ISBN 0-7356-2704-5.
  11. ^ "VOLATILE Attribute and Statement". Cray.
  12. ^ "Volatile and shared array in Fortran". Intel.com.
  13. ^ "VOLATILE". Oracle.com.

구문

1
volatile declarator ;
 

 

참고

 /volatile 컴파일러 스위치를 사용하여 컴파일러에서 이 키워드를 해석하는 방법을 수정할 수 있습니다.

 

Visual Studio는 대상 아키텍처에 따라 volatile 키워드를 다르게 해석합니다. ARM의 경우 /volatile 옵션을 지정하지 않으면 /volatile:isowere가 지정된 것처럼 컴파일러가 수행됩니다. ARM 이외의 아키텍처의 경우 /volatile 옵션을 지정하지 않으면 /volatile:ms가 지정된 것처럼 컴파일러가 수행됩니다. 따라서 ARM 이외의 아키텍처의 경우 스레드간에 공유되는 메모리를 처리 할 때, /volatile:iso를 지정하고 명시적 동기화 기본 형식 및 컴파일러 내장 함수를 사용하는 것이 좋습니다. 

 

volatile 한정자를 사용하여 인터럽트 처리기와 같은 비동기 프로세스에서 사용되는 메모리 위치에 대한 액세스를 제공 할 수 있습니다.

 

volatile 키워드가 __restrict 키워드에도 사용되면 변수가 우선 적용됩니다.

 

구조체(struct) 멤버가 volatile으로 표시되면 volatile이 전체 구조로 전파됩니다. 구조가 하나의 명령어를 사용하여 현재 아키텍처에서 복사 할 수 있는 길이를 갖지 않으면 해당 구조에서 volatile이 완전히 손실 될 수 있습니다.

 

volatile 키워드는 다음 조건 중 하나에 해당하는 경우 필드에 아무런 영향을 미치지 않을 수 있습니다.

  • 휘발성 필드의 길이는 하나의 명령어를 사용하여 현재 아키텍처에서 복사 할 수있는 최대 크기를 초과하는 경우.
  • 가장 바깥 쪽 구조체의 (길이 또는 가능한 중첩 구조체의 멤버인 경우) 길이는 한 명령을 사용하여 현재 아키텍처에서 복사 할 수 있는 최대 크기를 초과하는 경우.

프로세서가 캐시 할 수없는 메모리 액세스를 재정렬하지 않더라도 캐시 할 수없는 변수는 컴파일러가 메모리 액세스를 재정렬하지 않도록 volatile로 표시되어야합니다.

 

volatile로 선언된 객체는 언제든지 값이 변경 될 수 있으므로 특정 최적화에서는 사용되지 않습니다. 이전 명령이 동일한 객체의 값을 요청한 경우에도 시스템은 요청시 volatile 객체의 현재 값을 항상 읽습니다. 또한 객체의 값은 할당시 즉시 기록됩니다.

 

 

ISO 규격

C # volatile 키워드에 익숙하거나 이전 버전의 Microsoft C ++ 컴파일러 (MSVC)에서의 volatile 동작에 익숙한 경우 C++11 ISO 표준 volatile 키워드가 다르고 MSVC에서  /volatile:iso 컴파일러 옵션이 지정됩니다. (ARM의 경우 기본적으로 지정되어 있습니다.) C++11의 volatile 키워드는 하드웨어 액세스에만 사용됩니다. 스레드 간 통신에는 사용하지 마십시오. 스레드 간 통신의 경우  C++ Standard Library as std::atomic<T>와 같은 메커니즘을 사용하십시오.

 

ISO 규격의 끝

Microsoft 한정자

 

ARM 이외의 다른 아키텍처를 대상으로하는 경우 기본적으로 /volatile:ms 컴파일러 옵션을 사용하면 컴파일러에서 volatile 개체에 대한 참조 사이의 순서를 유지하고 다른 전역 개체에 대한 참조 순서를 유지 관리하는 추가 코드를 생성합니다. 특히:

  • volatile 객체에 대한 쓰기 (volatile 쓰기라고도 함)에는 릴리스의 의미(Release Semantics)가 있습니다. 즉, 명령 시퀀스에서 volatile 객체에 대한 쓰기 행위 전에 발생하는 전역 도는 정적 객체에 대한 참조는 컴파일 된 바이너리에서의 휘발성 쓰기 행위 전에 발생합니다.

  • volatile 객체의 읽기(volatile 읽기 라고도 함)에는 얻는 것의 의미(Acquire Semantics)가 있습니다. 즉, 명령 시퀀스에서 휘발성 메모리를 읽은 후에 발생하는 전역 또는 정적 객체에 대한 참조가 컴파일 된 이진 파일에서의 volatile 읽기 후에 발생합니다.

이를 통해 volatile 객체를 다중 스레드 응용 프로그램에서 메모리 잠금 및 릴리스에 사용할 수 있습니다.

 

 

 

참고

/volatile:ms 컴파일러 옵션을 사용할 때 제공되는 향상된 보증에 의존하는 경우 코드는 이식 할 수 없습니다.

참고

Keywords
const
const and volatile Pointers

 

 

예제_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// compile with: /EHsc /O2
#include <iostream>
#include <windows.h>
using namespace std;
volatile bool Sentinel = true;
int CriticalData = 0;
unsigned ThreadFunc1( void* pArguments ) 
    while (Sentinel) Sleep(0); // volatile spin lock 
 
    // CriticalData load guaranteed after every load of Sentinel 
    cout << "Critical Data = " << CriticalData << endl;
    return 0;
}
 
unsigned ThreadFunc2( void* pArguments ) 
{
    Sleep(2000);
    CriticalData++// guaranteed to occur before write to Sentinel
    Sentinel = false// exit critical section
    cout << "Critical Data = " << CriticalData << endl;
    return 0;
 
int main()
    HANDLE hThread1, hThread2;
    DWORD retCode;
    
    hThread1 = CreateThread(NULL0, (LPTHREAD_START_ROUTINE)&ThreadFunc1, NULL0NULL); 
    hThread2 = CreateThread(NULL0, (LPTHREAD_START_ROUTINE)&ThreadFunc2, NULL0NULL);
    
    if (hThread1 == NULL || hThread2 == NULL)
    { 
        if (hThread1 != NULL) CloseHandle(hThread1);
        if (hThread2 != NULL) CloseHandle(hThread2);
        
        cout << "CreateThread failed." << endl;
        return 1;
    } 
    
    retCode = WaitForSingleObject(hThread1,3000);
    
    CloseHandle(hThread1);
    CloseHandle(hThread2);
    
    if (retCode == WAIT_OBJECT_0 && CriticalData == 1 ) cout << "Success" << endl;
    else cout << "Failure" << endl
}
 
 

스레드 두 개를 준비하였고 하나는 스핀락을, 하나는 일정시간 대기 후 앞 스레드의 스핀락을 풀어주는 변수 값을 수정하여 스레드가 정상실행할 수 있게한다.

 

스핀락을 풀 때가 어찌보면 가장 큰 문제가 될 수 있는데, 해당 변수가 수정될 때 값을 가져오게 되면 과연 멀쩡한 값이 받아지냐는 것이다. 물론 bool 값처럼 간단한 자료형이면 처리가 쉽겠지만(상황에 따라서도 이 말은 썩 옳지 않을 수 있다.) 말이다. 문제는 컴파일러가 해당 코드를 어떻게 실행하느냐가 관건이다. volatile을 사용하게 될 경우 해당 변수를 수정하거나 여타 다른 일을 할 때에 설명에 나와있는 것처럼 다시 값을 최신화 하는 것 뿐만 아니라 이전에 쓰인 변수나 코드들을 정리하여 실행한다. 컴파일러에 따라서 후에 올 코드가 서로 연관된 변수나 함수를 사용하지 않는다 판단하면 비동기 환경 내에서는 내용이 바뀔 수 있다는 말이다.

 

 

 

간단한 예제를 보면서 마무리를 하겠다.

 

wiki 예제_

1
2
3
4
5
6
7
8
static int foo;
 
void bar(void) {
    foo = 0;
 
    while (foo != 255)
         ;
}
 

 

최적화 컴파일러가 위 코드를 아래와 같은 코드로 바꿀 것이라는 점이다. 근거는 차후에 foo에 저장된 값을 변경한다는 코드가 없으며, 항상 0과 같다고 가정해버린다는 설명이다.

 

1
2
3
4
5
6
void bar_optimized(void) {
    foo = 0;
 
    while (true)
         ;
}
 

 

따라서 해당 반복문은 true로 변형되어 해석이 되어버린다. 하지만 역시 volatile을 사용할 경우 해당 코드는 다시 읽는다는 과정을 거친다.

 

 

 

하지만 이 키워드는 멀티스레드(wiki)를 관리하는 경우 혹은 스레드 통신 간(inter-thread communication)에는 std::atomic<T>을 사용하라는 문구가 있다. 이러한 점을 보아 해당 키워드는 애초에 하드웨어, 단일 CPU 환경을 대상으로 설계된 것 같다.

반응형

'C++' 카테고리의 다른 글

Decltype (C++) (declare type)  (0) 2019.12.25
Mutable, Auto  (0) 2019.12.25
저장소 클래스 - Storage classes (static, extern, thread_local, register)  (0) 2019.12.25
Timer - 시간 측정기  (0) 2019.12.25
const*, constexpr**  (0) 2019.12.25
Posted by Kestone Black Box
,