C 언어 휘발성 및 const 키워드 분석

휘발성 물질

1. 키워드 설명 :

휘발성의 원래 의미는
레지스터에 대한 액세스가 메모리 장치에 대한 액세스보다 훨씬 빠르기 때문에 "휘발성"입니다 . 따라서 컴파일러는 일반적으로 메모리에 대한 액세스를 줄 이도록 최적화하지만 더티 데이터를 읽을 수 있습니다. 변수의 값을 선언하기 위해 휘발성이 필요한 경우 시스템은 이전 명령어가 방금 데이터를 읽은 경우에도 항상 위치하는 메모리에서 데이터를 읽습니다. 정확하게 말하면,이 키워드로 선언 된 변수를 발견하면 컴파일러는 더 이상 변수에 액세스하는 코드를 최적화하지 않아 특수 주소에 대한 안정적인 액세스를 제공합니다. 휘발성이 사용되지 않으면 컴파일러는 The 문이 최적화됩니다. (간결하게 말하면 volatile 키워드는 컴파일러의 컴파일 결과에 영향을줍니다. volatile로 선언 된 변수는 변수가 언제든지 변경 될 수 있음을 나타냅니다. 변수와 관련된 작업의 경우 오류를 방지하기 위해 컴파일 최적화를 수행하지 마십시오)

일반적으로 volatile은 다음과 같은 위치에서 사용됩니다
.1) 다른 프로그램에서 탐지하기 위해 인터럽트 서비스 프로그램에서 수정 된 변수는 휘발성을 추가해야합니다
.2) 멀티 태스킹 환경에서 작업간에 공유되는 플래그는 휘발성을 추가해야합니다
.3) 메모리 매핑 된 하드웨어 레지스터는 일반적으로 읽고 쓸 때마다 다른 의미를 가질 수 있기 때문에 휘발성 설명을 추가해야합니다.
이러한 상황에서 휘발성이 사용되는 이유를 설명하지 않고 출처를 살펴 보겠습니다.
하드웨어
메모리 액세스 속도가 CPU보다 훨씬 낮기 때문에 성능 향상을 위해 하드웨어 캐시 캐시를 하드웨어에 도입하여 메모리 액세스를 가속화합니다. CPU의 명령 실행은 반드시 엄격한 순서로 실행되는 것은 아니며 관련 명령이없는 경우 순서대로 실행하여 CPU의 명령 파이프 라인 (캐스케이드)을 최대한 활용하여 실행 속도를 높일 수 있습니다.
소프트웨어
1 차 최적화 : 하나는 코드를 작성할 때 프로그래머가 최적화하고 다른 하나는 컴파일러에서 최적화합니다. 일반적으로 사용되는 컴파일러 최적화 방법은 다음과 같습니다. 레지스터에 메모리 변수 캐시, CPU 명령 파이프 라인을 최대한 활용하기 위해 명령 순서 조정, 일반적인 방법은 읽기 및 쓰기 명령을 재정렬하는 것입니다. 기존 메모리를 최적화 할 때 이러한 최적화는 투명하고 효율적입니다. 컴파일러 최적화 또는 하드웨어 재정렬로 인해 발생하는 문제에 대한 해결책은 하드웨어 (또는 다른 프로세서)의 관점에서 특정 순서로 수행해야하는 작업간에 메모리 장벽을 설정하는 것입니다. ** void Barrier (void) **이 함수는 컴파일러에게 메모리 배리어를 삽입하라고 알려 주지만 하드웨어에는 적합하지 않습니다. 컴파일 된 코드는 현재 CPU 레지스터에서 수정 된 모든 값을 메모리에 저장합니다. 데이터가 필요합니다. 메모리에서 다시 읽습니다.
휘발성은 항상 최적화와 관련이 있습니다.
컴파일러에는 프로그램의 변수가 할당 된 위치, 사용 된 위치 및 실패한 위치를 분석하는 데이터 흐름 분석이라는 기술이 있습니다. 분석 결과는 지속적인 병합, 지속적인 전파에 사용할 수 있습니다. 및 기타 최적화, 일부는 추가로 제거 할 수 있습니다. 그러나 때로는 이러한 최적화가 프로그램에 필요한 것이 아닌 경우 volatile 키워드를 사용하여 이러한 최적화를 금지 할 수 있습니다.
2. volatile이 사용되어야하는 이유를 설명하는 간단한 예제
1) 컴파일러에게 최적화를 수행 할 수 없음을 알립니다.

int i;
i=1;
i=2;
i=3;

컴파일러가 컴파일 된 후 다음과 같이 최적화됩니다.

int i;
i=3;

할당 된 값 1, 2가 최적화되도록합니다. 원래 의도를 보장하려면 다음과 같아야합니다.

volatile int i;
i=1;
i=2;
i=3;

2) volatile로 정의 된 변수는 프로그램 외부에서 변경되며 매번 메모리에서 읽어야하며 캐시 또는 레지스터에있는 백업은 재사용 할 수 없습니다.
조건 : 위에서 언급 한 두 번째 포인트 인 멀티 태스킹 (멀티 스레딩)에서 멀티 스레딩에 플래그 비트가 있으며 수정이 가능합니다.

volatile int  a;
a=0;
while(!a){
    
    
do some things;
}
init();

다른 작업이 a를 수정하지 않으면 init () 함수가 실행되지 않고 프로그래머의 요구 사항에 따라 실행되며 volatile이 필요하지 않습니다. 반대로 다른 a의 값이 수정되면 작업, 실시간이 필요합니다. 레지스터의 데이터를 업데이트하려면 휘발성을 추가해야합니다.
3. 휘발성이 사용되는 세 가지 시나리오를 설명하려면 예제를 사용하십시오.
1) 다른 프로그램이 감지하기 위해 인터럽트 서비스 프로그램에서 수정 된 변수는 휘발성이어야합니다.

static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }void IRQ(void)//中断函数
{
    
    
      a=1;
}

프로그램의 원래 의도는 IRQ 인터럽트 발생시 main 함수에서 dosomething 함수를 호출하는 것이지만, 컴파일러는 main 함수에서 a가 수정되지 않았다고 판단하므로 a to에서 읽기 작업 만 수행 할 수 있습니다. 특정 레지스터, 그리고 매번 판정이이 레지스터의 "복사본"만 사용하는 경우 두 번째는 아무것도 호출되지 않도록합니다. 변수가 volatile로 수정되면 컴파일러는이 변수에 대한 읽기 및 쓰기 작업이 최적화되지 않도록 보장합니다 (확실히 실행 됨).
올바른 코드는 다음과 같습니다.

volatile static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }void IRQ(void)//中断函数
{
    
    
      a=1;
}

2) 멀티 태스킹 환경에서 태스크간에 공유되는 플래그는 휘발성을 추가해야합니다.
위의 예에서 설명한 것처럼
3) 메모리 매핑 된 하드웨어 레지스터는 읽기와 쓰기가 서로 다른 의미를 가질 수 있기 때문입니다.
장치가 초기화되고이 장치의 특정 레지스터 주소가 0xff800000이라고 가정합니다.

int  *output = (int *)0xff800000;
int   init(void)
{
    
    
      int i;
      for(i=0;i< 5;i++){
    
    
         *output = i;
}
}

컴파일 후 컴파일러는 이전 루프가 오랫동안 쓸모없고 최종 결과에 영향을 미치지 않는다고 생각합니다. 왜냐하면 결국 출력 포인터를 4에 할당하기 때문에 컴파일러 결과는 다음과 같습니다.

int  init(void)
{
    
    
      *output = 4;
}

위 코드와 같은 순서로 외부 장치를 초기화하면 분명히 최적화 프로세스가 목표를 달성하지 못할 것입니다. 반대로이 포트에 쓰기 작업을 반복하지 않고 읽기 작업을 반복하면 결과는 동일합니다. 컴파일러가 최적화 된 후 코드는이 주소를 한 번만 읽습니다. 그러나 코드 관점에서는 문제가 없습니다. 이때 volatile을 사용하여이 변수가 불안정하다는 것을 컴파일러에 알리고이 변수를 만날 때 최적화하지 않아야합니다.

volatile  int *output=(volatile  int *)0xff800000;

위의 세 가지 상황은 각각이 세 가지 방법을 사용하여 보호 할 수 있습니다
.1) 인터럽트를 꺼서 실현할 수 있습니다
.2) 작업 스케줄링이 금지됩니다
.3) 좋은 하드웨어 설계에만 의존 할 수 있습니다.
질문은
1) 매개 변수가 const 또는 volatile 일 수 있습니까?
예, 예를 들어 읽기 전용 상태 레지스터입니다. 예기치 않게 변경 될 수 있으므로 휘발성입니다. 프로그램이 수정하려고하지 않기 때문에 const입니다.
키워드 volatile로 수정 된 변수는 언제든지 수정할 수 있으므로 액세스 할 때마다 해당 메모리 주소를 검색해야합니다. const에 의해 수정 됨은 프로그래머가 수정할 수 없으며 시스템에 의해 수정 될 수 있음을 의미 할 수 있습니다.
2) 포인터가 휘발성 일 수 있습니까?
예, 중간 서비스 서브 루틴이 버퍼에 대한 포인터를 수정할 때.
4. 휘발성의 본질 :
1)이 스레드에서 변수를 읽을 때 액세스 속도를 향상시키기 위해 컴파일러는 최적화 할 때 변수를 레지스터로 읽습니다. 나중에 변수 값을 검색 할 때 값은 다음과 같습니다. 레지스터에서 직접 가져옵니다.이 스레드에서 변수 값이 변경되면 일관성을 유지하기 위해 변수의 새 값이 레지스터에 동시에 복사됩니다.

2) 다른 쓰레드 등에 의해 변수의 값이 변하면 레지스터의 값이 그에 따라 변하지 않아 응용 프로그램에서 읽은 값이 실제 변수 값과 일치하지 않게됩니다.

3)이 레지스터의 값이 다른 스레드 등에 의해 변경 될 경우 원래 변수의 값이 변경되지 않아 응용 프로그램에서 읽은 값이 실제 변수 값과 일치하지 않게됩니다.
다시 예를 들어

int square(volatile int *ptr)
{
    
    
return *ptr * *ptr;
}

목적은 포인터 ptr이 가리키는 값의 제곱 을 반환 하는 것입니다 . ptr은 휘발성 매개 변수를 가리 키 므로 컴파일러는 다음과 유사한 코드를 생성합니다.

int square(volatile int *ptr)
{
    
    
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

* ptr의 값이 예기치 않게 변경 될 수 있으므로 a와 b가 다를 수 있습니다 (다른 스레드가 없거나 외부 조건이 * ptr을 변경하지 않으면이 문장이 맞습니다!). 결과적으로이 코드는 예상 한 제곱 값을 반환하지 않을 수 있습니다! 올바른 코드는 다음과 같습니다.

int square(volatile int *ptr)
{
    
    
int a;
a = *ptr;
return a * a;
}

참고 : volatile을 자주 사용하면 코드 크기가 증가하고 성능이 저하 될 수 있으므로 volatile을 합리적으로 사용하십시오.

요약 :
휘발성 변수에는 두 가지 기능이 있습니다.
1) 컴파일러에게 최적화하지 않도록 지시합니다
.2) 시스템에 캐시 또는 레지스터 (휘발성 사용)에서 변수 값을 가져 오는 대신 항상 메모리에서 변수 주소를 가져 오도록 지시합니다. 휘발성 시스템없이 캐시 생성)

const

설명
const 키워드는 상수를 정의하는 데 사용됩니다. 변수가 const에 의해 수정되면 더 이상 값을 변경할 수 없습니다. 수정 된 항목을 보호하고 실수로 인한 수정을 방지하며 프로그램의 견고성을 높일 수 있습니다.
미리 컴파일 된 명령어와 비교하면 const 수정 자에는 다음과 같은 장점이 있습니다.
1) 사전 컴파일 된 명령어는 값의 간단한 교체 만 수행하고 유형 검사를 수행 할 수 없습니다.
확인 :
상수 정적 문자열을 수정하는 데 사용되는 const를보십시오 . 예 :
const char * str = "fdsafdsa";
만약 const 수정없이 str [4] = 'x'문을 나중에 의도적으로 또는 의도하지 않게 작성하면 읽기 전용 메모리 영역이 할당되고 프로그램이 즉시 비정상적으로 종료됩니다. 이 오류는 컴파일 할 때 체크 아웃 할 수 있으며 이는 const를 사용하는 장점입니다. 컴파일 타임에 논리 오류를 찾을 수 있습니다.

2) 수정 된 내용을 보호하고 우발적 인 수정을 방지하며 프로그램의 견고성을 향상시킬 수 있습니다
.3) 컴파일러는 일반적으로 일반적인 const 상수에 대한 저장 공간을 할당하지 않고 심볼 테이블에 저장하므로 컴파일이됩니다. 메모리를 저장하고 읽는 작업이없는 시간 상수는 매우 효율적입니다.
4) 매크로 정의의 범위는 현재 파일로 제한됩니다. 기본적으로 const 객체는 파일에서만 유효하며 같은 이름의 const 변수가 여러 파일에 나타날 경우 다른 파일에서 독립 변수를 정의하는 것과 같습니다. 여러 파일간에 const 객체를 공유하려면 변수 정의 앞에 extern 키워드를 추가해야합니다 (선언 및 정의 모두에서).
1. 상수 수정 변수

const int n=1;
int const n=1;

이 두 가지 쓰기 방법은 동일합니다. 둘 다 변수 n의 값을 변경할 수 없음을 의미합니다. const를 사용하여 변수를 수정할 때 얼굴 변경을 초기화해야합니다. 그렇지 않으면 나중에 할당이 불가능합니다. .
2. 상수 포인터와 포인터 상수
는 다음과 같이 표현할 수 있습니다.

const int * n;
int const * n;

1) 상수 포인터는이 포인터를 통해 변수의 값을 변경할 수 없지만 다른 참조를 통해 변수의 값을 변경할 수 있음을 나타냅니다.

int i=5;
const int* n=&i;
i=6;

상수 포인터가 가리키는 값은 변경할 수 없지만 이것은 포인터 자체를 변경할 수없고 상수 포인터가 다른 주소를 가리킬 수 있다는 의미는 아닙니다.

int a=5;
int b=6;
const int* n=&a;
n=&b;

2) 포인터 상수는 포인터 자체가 상수이며 다른 주소를 가리킬 수 없음을 나타냅니다.

int *const n;

포인터 상수가 가리키는 주소는 변경할 수 없지만 주소에 저장된 값은 변경 될 수 있으며 변경된 주소에 대한 다른 포인터에 의해 수정 될 수 있습니다. 상수 포인터와 포인터 상수를 구별하는 열쇠는 별표의 위치입니다. 우리는 별표를 구분선으로 사용합니다. const가 별표의 왼쪽에 있으면 상수 포인터이고 const가 오른쪽에 있으면 상수 포인터입니다. 별표의 측면에서는 포인터 상수입니다. 별표를 "포인터"로, const를 "상수"로 읽으면 내용은 정확히 동일합니다.

int const*n;是常量指针
int*const n;是指针常量

상수에 대한 상수 포인터는 위의 두 가지를 조합 한 것입니다. 포인터가 가리키는 위치는 변경할 수 없으며이 포인터를 통해 변수의 값을 변경할 수 없습니다. 그러나 변수의 값은 여전히 ​​다른 일반적인 방법을 통해 변경할 수 있습니다. 포인터.

const int* const p;

3) 함수의 매개 변수
수정 1) 포인터가 가리키는 내용
수정 방지 2) 포인터가 가리키는 주소 수정 방지
3) 위의 두 가지 조합.
4) 함수의 반환 값
수정 "포인터 전송"모드에서 함수의 반환 값을 const로 수정하면 함수 반환 값 (포인터)의 내용은 수정할 수 없으며 반환 값은 const 수정 된 Type 포인터와 동일하게 할당됩니다.
5)
전역 변수 수정 전역 변수의 범위는 전체 파일입니다. 함수가 전역 변수의 값을 변경하면이 변수를 참조하는 다른 함수에도 영향을 미치므로 전역 변수 사용을 피해야합니다. 버그 예외 전역 변수를 사용해야한다면 const 한정자를 사용하여 수정해야한다는 사실을 찾기 어렵습니다. 따라서 불필요한 사람의 수정을 방지하기 위해 사용되는 방법은 지역 변수와 동일합니다.
3.
const 사용에 대한 몇 가지 제안 1) const를 대담하게 사용하면 끝없는 이점을 얻을 수 있지만 그 이유를 파악해야합니다
.2) const 변수 할당과 같은 가장 일반적인 할당 오류를 방지하려면 다음을 참조하십시오. 자세한 내용은 생각하고 질문;
3) 위와 같은 이유로 대신 일반 객체 인스턴스의 참조 또는 포인터를 사용한다 매개 변수에 const를 사용,
멤버 함수 (매개 변수, 반환 값의 형 구조 4) 세 가지 용도, 기능)입니다 매우 중요한 좋은 사용,
5) 쉽게 const를 같은 함수의 반환 값 유형 설정하지 마십시오
과부하 사업자 제외) 6, 일반적으로 객체에 const를 참조로 반환 값 유형을 설정하지 마십시오;
7) 모든 데이터가 의지 수정되지 않음 모든 멤버 함수는 const 유형으로 선언되어야합니다.

const 및 volatile 요약

1. 객체는 const와 volatile로 동시에 수정 될 수 있으며, 이는이 객체가 일정한 의미를 구현하고 있음을 나타내지 만 동시에 현재 객체가 위치한 프로그램 컨텍스트에서 예기치 않은 상황에 의해 수정 될 수 있습니다.
2. volatile 키워드로 수정 된 변수는 언제든지 수정할 수 있으므로 액세스 할 때마다 해당 메모리 주소를 검색해야합니다. const에 의해 수정 됨은 프로그래머가 수정할 수 없으며 시스템에 의해 수정 될 수 있음을 의미 할 수 있습니다.
3. const와 volatile은 변수 또는 함수 매개 변수 선언에 사용되는 유사한 구문을 가진 유형 수정 자이며 비 정적 멤버 함수를 제한 할 수도 있습니다. 이와 함께 선언 된 유형 변수는 운영 체제, 하드웨어 또는 기타 스레드와 같이 컴파일러에 알려지지 않은 일부 요인에 의해 변경 될 수 있습니다. 이 키워드로 선언 된 변수를 발견하면 컴파일러는 더 이상 변수에 액세스하는 코드를 최적화하지 않으므로 특수 주소에 대한 안정적인 액세스를 제공하고 컴파일러가 휘발성 변수를 작동하기위한 명령 순서를 조정하지 못하도록 할 수 있습니다. volatile로 선언 된 변수의 값이 필요한 경우 시스템은 이전 명령어가 방금 데이터를 읽었더라도 항상 해당 변수가있는 메모리에서 데이터를 읽습니다. 그리고 읽은 데이터는 즉시 저장됩니다.
4. 휘발성 포인터 : 1. 포인터가 가리키는 객체를 수정하고 데이터는 const 또는 volatile입니다 : const char * cpch; volatile char * vpch ;; 2. 포인터 자체의 값-주소를 나타내는 정수 변수 , 이는 const 또는 Volatile : char * const pchc; char * volatile vchc ;.

추천

출처blog.csdn.net/weixin_42271802/article/details/105973024