는 무엇을 않습니다 volatile
할 키워드? C ++에서 어떤 문제가 해결됩니까?
제 경우에는 의도적으로 필요하지 않았습니다.
volatile
효과적인 평신도 용어로 정리하여 효과적으로 사용할 수 있는 예를 보여주는 훌륭한 자료입니다 . 링크 : Publications.gbdirect.co.uk/c_book/chapter8/…
volatile
보다 더 유용 friend
합니다.
는 무엇을 않습니다 volatile
할 키워드? C ++에서 어떤 문제가 해결됩니까?
제 경우에는 의도적으로 필요하지 않았습니다.
volatile
효과적인 평신도 용어로 정리하여 효과적으로 사용할 수 있는 예를 보여주는 훌륭한 자료입니다 . 링크 : Publications.gbdirect.co.uk/c_book/chapter8/…
volatile
보다 더 유용 friend
합니다.
답변:
volatile
완전히 분리 된 프로세스 / 장치 / 무엇을 쓸 수 있는지 메모리의 특정 지점에서 읽는 경우 필요합니다.
나는 C에서 멀티 프로세서 시스템에서 듀얼 포트 램을 사용했습니다. 우리는 다른 사람이 언제 완료되었는지 알기 위해 세마포어로 16 비트 값으로 관리되는 하드웨어를 사용했습니다. 본질적으로 우리는 이것을했습니다 :
void waitForSemaphore()
{
volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}
이 없으면 volatile
옵티마이 저는 루프를 쓸모없는 것으로 간주합니다 (남자는 절대 값을 설정하지 않습니다! 그는 견과류를 제거하고 해당 코드를 제거하십시오!). 내 코드는 세마포어를 얻지 않고 계속 진행되어 나중에 문제가 발생합니다.
uint16_t* volatile semPtr
대신 작성 하면 어떻게됩니까? 이는 포인터 자체를 검사하는 등, 예를 들어 semPtr == SOME_ADDR
최적화되지 않을 수 있도록 포인터를 휘발성으로 표시해야합니다 (예 : 값 대신) . 그러나 이것은 또한 휘발성 지적 가치를 다시 의미합니다. 아니?
volatile
내장 시스템 또는 장치 드라이버를 개발할 때 메모리 매핑 된 하드웨어 장치를 읽거나 써야합니다. 특정 장치 레지스터의 내용은 언제든지 변경 될 수 있으므로 volatile
이러한 액세스가 컴파일러에 의해 최적화되지 않도록 키워드가 필요합니다 .
일부 프로세서에는 64 비트 이상의 정밀도를 갖는 부동 소수점 레지스터가 있습니다 (예 : SSE가없는 32 비트 x86, Peter의 의견 참조). 이렇게하면 배정 밀도 숫자로 여러 연산을 실행하면 실제로 각 중간 결과를 64 비트로 자르는 것보다 더 정밀한 답변을 얻을 수 있습니다.
이것은 일반적으로 훌륭하지만 컴파일러가 레지스터를 할당하고 최적화를 수행 한 방법에 따라 정확히 동일한 입력에 대해 동일한 작업에 대해 다른 결과를 갖게됨을 의미합니다. 일관성이 필요한 경우 volatile 키워드를 사용하여 각 작업을 강제로 메모리로 되돌릴 수 있습니다.
대수적으로 이해하지 않지만 Kahan summation과 같은 부동 소수점 오류를 줄이는 일부 알고리즘에도 유용합니다. 대수적으로 그것은 nop이므로 일부 중간 변수가 휘발성이 아닌 한 종종 잘못 최적화됩니다.
volatile double
대신 부동 소수점 변수를 선언하여이를 해결했습니다 double
. 부동 소수점 오차의 추가 과장으로 인해 결과는 실질적으로 달랐습니다.
g++ -mfpmath=sse
32 비트 x86에도 사용하는 것과 같은 옵션이 있습니다 . 당신은 사용할 수 있습니다 gcc -ffloat-store
반올림 강제로 모든 곳에서 x87을 사용하는 경우에도, 또는 53 비트 가수로 x87 정밀도를 설정할 수 있습니다 randomascii.wordpress.com/2012/03/21/...를 .
volatile
어디에서나 이점을 잃지 않고 몇 가지 특정 위치에서 반올림 하는 데 사용할 수 있습니다.
A로부터 "휘발성 약속으로" 단 삭스에 의해 기사 :
(...) 휘발성 개체는 값이 자연스럽게 변경 될 수있는 개체입니다. 즉, 객체를 휘발성으로 선언하면 프로그램의 명령문이 객체를 변경하는 것으로 보이지 않더라도 객체가 상태를 변경할 수 있음을 컴파일러에 알립니다. "
다음은 volatile
키워드 에 관한 세 가지 기사의 링크입니다 .
잠금없는 데이터 구조를 구현할 때는 반드시 휘발성을 사용해야합니다. 그렇지 않으면 컴파일러는 변수에 대한 액세스를 자유롭게 최적화하여 의미를 변경합니다.
달리 말하면, volatile은이 변수에 액세스하는 컴파일러가 실제 메모리 읽기 / 쓰기 작업에 해당해야한다는 것을 컴파일러에 알려줍니다.
예를 들어, Win32 API에서 InterlockedIncrement를 선언하는 방법은 다음과 같습니다.
LONG __cdecl InterlockedIncrement(
__inout LONG volatile *Addend
);
std::atomic<LONG>
하므로 더 이상 사용되지 않으므로 순수한로드 / 순수한 상점을 최적화하거나 재정렬 또는 기타 다른 문제없이 잠금없는 코드를보다 안전하게 작성할 수 있습니다.
1990 년대 초에 작업했던 대규모 응용 프로그램에는 setjmp 및 longjmp를 사용하는 C 기반 예외 처리가 포함되었습니다. 휘발성 키워드는 "catch"절 역할을하는 코드 블록에 값을 유지해야하는 변수에 필요했습니다. 이러한 변수는 레지스터에 저장되고 longjmp에 의해 삭제되지 않습니다.
표준 C에서 사용할 장소 중 하나 volatile
는 신호 처리기입니다. 실제로 표준 C에서 신호 처리기에서 안전하게 할 수있는 모든 것은 volatile sig_atomic_t
변수를 수정 하거나 빠르게 종료하는 것입니다. 실제로 AFAIK는 표준 C에서 volatile
정의되지 않은 동작을 피하기 위해 사용해야하는 유일한 장소입니다 .
ISO / IEC 9899 : 2011 §7.14.1.1
signal
기능¶5
abort
또는raise
함수 호출의 결과가 아닌 다른 신호가 발생하는 경우, 신호 핸들러가 값을 지정하는 것 이외의 잠금없는 원자 오브젝트가 아닌 정적 또는 스레드 저장 기간을 갖는 오브젝트를 참조하면 동작이 정의되지 않습니다. 로 선언 된 객체volatile sig_atomic_t
에 신호 를 보내 거나 신호 핸들러는 표준 라이브러리에서abort
함수,_Exit
함수,quick_exit
함수 또는signal
첫 번째 인수 가 함수를 호출하는 원인이되는 신호에 해당하는 신호 번호와 다른 함수 이외의 함수를 호출합니다. 매니저. 또한 이러한signal
함수 호출로 인해 SIG_ERR 리턴이 발생하면 값errno
이 결정되지 않습니다. 252)252) 비동기 신호 처리기에서 신호를 생성하면 동작이 정의되지 않습니다.
이는 표준 C에서 다음을 작성할 수 있음을 의미합니다.
static volatile sig_atomic_t sig_num = 0;
static void sig_handler(int signum)
{
signal(signum, sig_handler);
sig_num = signum;
}
그다지 많지 않습니다.
POSIX는 신호 처리기에서 수행 할 수있는 작업에 대해 훨씬 관대하지만 여전히 제한 사항이 있습니다 (한 가지 제한 사항 중 하나는 표준 I / O 라이브러리 (예 : printf()
기타)는 안전하게 사용할 수 없음).
임베디드를 개발하기 위해 인터럽트 핸들러에서 변경할 수있는 변수를 확인하는 루프가 있습니다. "휘발성"이 없으면 루프가 스눕이됩니다. 컴파일러가 알 수있는 한 변수는 절대 변경되지 않으므로 검사가 최적화됩니다.
더 전통적인 환경의 다른 스레드에서 변경 될 수있는 변수에도 동일한 내용이 적용되지만 동기화 호출을 수행하는 경우가 많으므로 컴파일러에는 최적화가 자유롭지 않습니다.
의도 한대로 사용하는 것 외에도 휘발성은 (템플릿) 메타 프로그래밍에 사용됩니다. const와 같은 휘발성 속성이 과부하 해결에 참여하므로 우발적 인 과부하를 방지하는 데 사용할 수 있습니다.
template <typename T>
class Foo {
std::enable_if_t<sizeof(T)==4, void> f(T& t)
{ std::cout << 1 << t; }
void f(T volatile& t)
{ std::cout << 2 << const_cast<T&>(t); }
void bar() { T t; f(t); }
};
이것은 합법적입니다. 두 과부하 모두 호출 가능하고 거의 동일합니다. volatile
우리는 바가 비 휘발성을 통과하지 않는다는 것을 알고 있으므로 과부하 의 캐스트 는 합법적 T
입니다. 그러나 volatile
버전은 엄청나게 나쁘므로 비 휘발성 f
을 사용할 수있는 경우 과부하 해결에서 선택하지 마십시오 .
코드는 실제로 volatile
메모리 액세스에 의존하지 않습니다 .
volatile
키워드는 컴파일러에 의해 결정될 수없는 방식으로 변경할 수있는 객체에 어떤 최적화를 적용에서 컴파일러를 방지하기위한 것입니다.
로 선언 된 객체는 volatile
현재 코드 범위를 벗어난 코드로 언제든지 값을 변경할 수 있으므로 최적화에서 생략됩니다. volatile
이전 명령이 동일한 객체의 값을 요청한 경우에도 시스템은 요청 된 시점에 임시 레지스터에 값을 유지하지 않고 항상 메모리 위치에서 객체 의 현재 값을 읽습니다 .
다음과 같은 경우를 고려하십시오
1) 범위 밖의 인터럽트 서비스 루틴에 의해 수정 된 글로벌 변수.
2) 멀티 스레드 응용 프로그램 내의 전역 변수.
휘발성 한정자를 사용하지 않으면 다음과 같은 문제가 발생할 수 있습니다.
1) 최적화를 설정하면 코드가 예상대로 작동하지 않을 수 있습니다.
2) 인터럽트가 활성화되고 사용될 때 코드가 예상대로 작동하지 않을 수 있습니다.
https://ko.wikipedia.org/wiki/Volatile_(computer_programming)
휘발성 키워드가 일부 변수에 대한 액세스를 (즉,이 스레드 또는 인터럽트 루틴에 의해 변경 될 수 있습니다) 최적화되지 컴파일러 말에 사용된다는 사실 게다가, 또한 수 있습니다 일부 컴파일러 버그를 제거하는 데 사용 - YES가 수 --- 이어야합니다 .
예를 들어 컴파일러가 변수 값과 관련하여 잘못된 가정을하고 있었기 때문에 임베디드 플랫폼에서 작업했습니다. 코드가 최적화되지 않은 경우 프로그램이 정상적으로 실행됩니다. 최적화 (필수 루틴이기 때문에 실제로 필요)를 사용하면 코드가 제대로 작동하지 않습니다. (정확하지는 않지만) 유일한 해결책은 'faulty'변수를 휘발성으로 선언하는 것입니다.
volatile
키워드 없이도 프로그램이 작동하는 것 같 습니까? 아마도 이것이 이유입니다.
앞에서 언급했듯이 volatile
키워드는 다음과 같은 경우에 도움이됩니다.
volatile int* p = ...; // point to some memory
while( *p!=0 ) {} // loop until the memory becomes zero
그러나 외부 또는 인라인이 아닌 함수가 호출되면 거의 영향을 미치지 않는 것 같습니다. 예 :
while( *p!=0 ) { g(); }
그런 다음 volatile
거의 동일한 결과가 있거나없는 결과가 생성됩니다.
g ()가 완전히 인라인 될 수있는 한, 컴파일러는 진행중인 모든 것을보고 따라서 최적화 할 수 있습니다. 그러나 프로그램이 컴파일러가 무슨 일이 일어나고 있는지 볼 수없는 곳을 호출하면 컴파일러가 더 이상 가정을하는 것이 안전하지 않습니다. 따라서 컴파일러는 항상 메모리에서 직접 읽는 코드를 생성합니다.
그러나 함수 g ()가 인라인이 될 때 (명시적인 변경이나 컴파일러 / 링커 영리함으로 인해) volatile
키워드 를 잊어 버린 경우 코드가 손상 될 수 있습니다 .
따라서 volatile
프로그램이 없어도 키워드 를 추가하는 것이 좋습니다 . 향후 변경과 관련하여 의도를보다 명확하고 강력하게 만듭니다.
volatile
void (* volatile fun_ptr)() = fun; fun_ptr();
C의 초기에는 컴파일러가 lvalue를 읽고 쓰는 모든 작업을 메모리 작업으로 해석하여 코드에 나타난 읽기 및 쓰기와 동일한 순서로 수행됩니다. 컴파일러에 연산 순서를 재정렬하고 통합 할 수있는 자유가 주어진 경우 많은 경우 효율성이 크게 향상 될 수 있지만 이에 문제가있었습니다. 심지어 작업은 그들을 지정할 필요했다 단지 때문에 종종 특정 순서로 지정된 일부 순서, 따라서 프로그래머는 항상 그런 것은 아니었다 그 많은 동등하게-좋은 대안 중 하나를 골랐다. 때로는 특정 작업이 특정 순서로 발생하는 것이 중요합니다.
시퀀싱에 대한 정확한 세부 사항은 대상 플랫폼 및 응용 분야에 따라 다릅니다. 표준은 특히 세부적인 제어를 제공하기보다는 간단한 모델을 선택했습니다. 일련의 액세스가 규정되지 않은 lvalue로 수행되는 volatile
경우, 컴파일러는 적합하다고 판단 될 때이를 재정렬하고 통합 할 수 있습니다. - volatile
정규화 된 lvalue로 조치를 수행하는 경우 , 품질 구현은 비표준 구문을 사용할 필요없이 의도 된 플랫폼 및 애플리케이션 필드를 대상으로하는 코드에 의해 필요한 추가 주문 보장을 제공해야합니다.
불행히도 프로그래머가 필요로하는 것이 무엇인지 확인하는 대신 많은 컴파일러가 표준에 의해 요구되는 최소한의 보증을 제공하기로 선택했습니다. 이것은 필요한 volatile
것보다 훨씬 덜 유용합니다. 예를 들어 gcc 또는 clang에서 기본 "핸드 오프 뮤텍스"를 구현해야하는 프로그래머 ([뮤텍스를 획득하고 릴리스 한 작업이 다른 작업을 수행 할 때까지 다시 수행하지 않는 경우]) 하나를 수행해야합니다. 네 가지 중 :
컴파일러가 인라인 할 수없고 전체 프로그램 최적화를 적용 할 수없는 함수에 뮤텍스의 획득 및 릴리스를 배치하십시오.
뮤텍스에 의해 보호되는 모든 객체를 뮤텍스를 volatile
획득 한 후 릴리스하기 전에 모든 액세스가 발생하는 경우 필요하지 않은 것으로 규정하십시오.
정규화되지 않은 모든 객체는 것처럼 코드를 생성하는 컴파일러를 강제로 최적화 레벨 0을 사용하여 register
있습니다 volatile
.
gcc 특정 지시문을 사용하십시오.
반대로, icc와 같은 시스템 프로그래밍에 더 적합한 고품질 컴파일러를 사용하는 경우 다른 옵션이 있습니다.
volatile
획득 또는 릴리스가 필요한 모든 장소 에서 -qualified 쓰기가 수행 되는지 확인하십시오 .기본 "hand-off mutex"를 얻으 volatile
려면 읽기 가 필요하고 (준비되었는지 확인하기 위해) volatile
쓰기가 필요하지 않아야합니다 (다른 쪽은 다시 넘길 때까지 다시 획득하려고 시도하지 않음). 의미없는 volatile
쓰기를 수행하는 것은 gcc 또는 clang에서 사용 가능한 옵션보다 여전히 낫습니다.
모든 답변이 우수합니다. 그러나 그 위에 모범을 보이고 싶습니다.
아래는 약간의 cpp 프로그램입니다 :
#include <iostream>
int x;
int main(){
char buf[50];
x = 8;
if(x == 8)
printf("x is 8\n");
else
sprintf(buf, "x is not 8\n");
x=1000;
while(x > 5)
x--;
return 0;
}
이제 위 코드의 어셈블리를 생성 해 보겠습니다 (여기서 관련된 부분 만 붙여 넣겠습니다).
어셈블리를 생성하는 명령 :
g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp
그리고 어셈블리 :
main:
.LFB1594:
subq $40, %rsp #,
.seh_stackalloc 40
.seh_endprologue
# assembly.cpp:5: int main(){
call __main #
# assembly.cpp:10: printf("x is 8\n");
leaq .LC0(%rip), %rcx #,
# assembly.cpp:7: x = 8;
movl $8, x(%rip) #, x
# assembly.cpp:10: printf("x is 8\n");
call _ZL6printfPKcz.constprop.0 #
# assembly.cpp:18: }
xorl %eax, %eax #
movl $5, x(%rip) #, x
addq $40, %rsp #,
ret
.seh_endproc
.p2align 4,,15
.def _GLOBAL__sub_I_x; .scl 3; .type 32; .endef
.seh_proc _GLOBAL__sub_I_x
sprintf
컴파일러 x
는 프로그램 외부에서 변경되지 않는다고 가정했기 때문에 어셈블리 코드가 생성되지 않았 음을 어셈블리에서 볼 수 있습니다 . while
루프도 마찬가지입니다 . while
컴파일러는 쓸모없는 코드로보고, 따라서 직접 할당하기 때문에 루프는 모두 인해 최적화에 제거 5
에 x
(참조 movl $5, x(%rip)
).
외부 프로세스 / 하드웨어가 와 x
사이 의 값을 변경하면 어떻게 됩니까 ? 블록이 작동 할 것으로 예상 되지만 불행히도 컴파일러는 해당 부분을 제거했습니다.x = 8;
if(x == 8)
else
자,에,이 문제를 해결하기 위해 assembly.cpp
, 우리가 변경할 수 int x;
에 volatile int x;
빠르게 생성 된 어셈블리 코드를 참조하십시오
main:
.LFB1594:
subq $104, %rsp #,
.seh_stackalloc 104
.seh_endprologue
# assembly.cpp:5: int main(){
call __main #
# assembly.cpp:7: x = 8;
movl $8, x(%rip) #, x
# assembly.cpp:9: if(x == 8)
movl x(%rip), %eax # x, x.1_1
# assembly.cpp:9: if(x == 8)
cmpl $8, %eax #, x.1_1
je .L11 #,
# assembly.cpp:12: sprintf(buf, "x is not 8\n");
leaq 32(%rsp), %rcx #, tmp93
leaq .LC0(%rip), %rdx #,
call _ZL7sprintfPcPKcz.constprop.0 #
.L7:
# assembly.cpp:14: x=1000;
movl $1000, x(%rip) #, x
# assembly.cpp:15: while(x > 5)
movl x(%rip), %eax # x, x.3_15
cmpl $5, %eax #, x.3_15
jle .L8 #,
.p2align 4,,10
.L9:
# assembly.cpp:16: x--;
movl x(%rip), %eax # x, x.4_3
subl $1, %eax #, _4
movl %eax, x(%rip) # _4, x
# assembly.cpp:15: while(x > 5)
movl x(%rip), %eax # x, x.3_2
cmpl $5, %eax #, x.3_2
jg .L9 #,
.L8:
# assembly.cpp:18: }
xorl %eax, %eax #
addq $104, %rsp #,
ret
.L11:
# assembly.cpp:10: printf("x is 8\n");
leaq .LC1(%rip), %rcx #,
call _ZL6printfPKcz.constprop.1 #
jmp .L7 #
.seh_endproc
.p2align 4,,15
.def _GLOBAL__sub_I_x; .scl 3; .type 32; .endef
.seh_proc _GLOBAL__sub_I_x
여기에서 조립에 대한 코드를 볼 수 있습니다 sprintf
, printf
그리고 while
루프가 생성되었다. x
외부 프로그램이나 하드웨어에 의해 변수가 변경 sprintf
되면 코드의 일부가 실행 된다는 이점이 있습니다. 그리고 유사하게 while
루프는 이제 바쁜 대기를 위해 사용될 수 있습니다.
다른 답변은 이미 다음과 같은 목적으로 일부 최적화를 피한다고 언급했습니다.
휘발성은 외부에서 나오고 예측할 수없는 것처럼 보일 필요가 있고 알려진 값을 기준으로 컴파일러 최적화를 피하고 결과가 실제로 사용되지 않지만 계산되거나 사용되어야하는 경우에는 필수적입니다. 벤치 마크를 위해 여러 번 계산하고 정확한 지점에서 시작하고 종료하려면 계산이 필요합니다.
휘발성 읽기는 입력 작업 (유사 scanf
또는 사용 cin
) 과 유사 합니다. 값은 프로그램 외부에서 온 것으로 보이 므로 값에 종속 된 계산은 그 이후에 시작해야합니다 .
휘발성 쓰기는 출력 작업과 유사합니다 ( printf
의 사용 또는 사용 cout
). 값은 프로그램 외부에서 전달되는 것 같습니다 . 따라서 값이 계산에 의존하는 경우 이전에 완료해야 합니다.
따라서 한 쌍의 휘발성 읽기 / 쓰기를 사용하여 벤치 마크를 길들이고 시간 측정을 의미있게 할 수 있습니다 .
휘발성이 없으면 컴파일러에서 계산을 시작할 수 있습니다 . 시간 측정과 같은 함수를 사용하여 계산 순서를 변경하는 것은 없습니다 .