무엇을 않는 register
C 언어에서 할 키워드? 최적화에 사용되었지만 표준에 명확하게 정의되어 있지 않다는 것을 읽었습니다. 여전히 관련이 있습니까? 그렇다면 언제 사용 하시겠습니까?
register
변수 의 주소를 얻으십시오 .
무엇을 않는 register
C 언어에서 할 키워드? 최적화에 사용되었지만 표준에 명확하게 정의되어 있지 않다는 것을 읽었습니다. 여전히 관련이 있습니까? 그렇다면 언제 사용 하시겠습니까?
register
변수 의 주소를 얻으십시오 .
답변:
컴파일러에 변수가 많이 사용되며 가능한 경우 프로세서 레지스터에 보관할 것을 권장합니다.
대부분의 최신 컴파일러는 자동으로 수행하므로 인간보다 컴파일러를 선택하는 것이 좋습니다.
register
변수 의 주소 가 취해지지 않도록 컴파일러가 필요합니다 . 이것이 키워드 의 유일한 필수 효과입니다 register
. 이 기능 내에서만 변수를 수정할 수 있음을 알리는 것이 쉽지 않기 때문에 이것은 최적화를 개선하기에 충분합니다.
컴파일러가 레지스터가 아닌 메모리에 변수를 유지하기로 결정하더라도 레지스터 변수의 주소를 사용할 수 없다고 언급 한 사람이 아무도 없습니다.
따라서 register
당신을 사용 하면 아무것도 얻지 못하고 (어쨌든 컴파일러는 변수를 넣을 위치를 스스로 결정할 것입니다) &
연산자를 잃을 이유가 없습니다.
register
컴파일러가 레지스터에 넣지 않아도 유용합니다.
const
는 아무것도 얻지 못하기 때문에 쓸모가 없으며 변수를 변경하는 기능 만 잃어 버립니다. register
미래에 아무도 생각하지 않고 변수의 주소를 사용하지 않도록하는 데 사용될 수 있습니다. register
그래도 사용할 이유가 없었습니다 .
컴파일러는 변수를 저장하기 위해 RAM 대신 CPU 레지스터를 사용하도록 컴파일러에 지시합니다. 레지스터는 CPU에 있으며 RAM보다 액세스 속도가 훨씬 빠릅니다. 그러나 이것은 컴파일러에 대한 제안 일뿐이며 따르지 않을 수도 있습니다.
나는이 질문이 C에 관한 것이라는 것을 알고 있지만 C ++에 대한 동일한 질문 이이 질문의 정확한 복제본으로 닫혔습니다. 따라서이 답변은 C에는 적용되지 않을 수 있습니다.
C ++ 11 표준의 최신 초안 인 N3485 는 이것을 7.1.1 / 3에서 말합니다
register
지정 이렇게 선언 된 변수가 많이 사용됩니다한다는 것을 알 수 있습니다. [ note : 힌트는 무시할 수 있으며 대부분의 구현에서 변수의 주소를 가져 오면 무시됩니다. 이 사용은 더 이상 사용되지 않습니다 ... — 끝 참고 ]
C ++에서 (하지만 하지 C에서), 표준하지 않습니다 상태는 변수의 주소를 취할 수 없음을 선언 register
; 그러나 수명 동안 CPU 레지스터에 저장된 변수에는 연관된 메모리 위치가 없으므로 해당 주소를 가져 오려고 시도하면 유효하지 않으며 컴파일러는 register
키워드를 무시 하여 주소를 가져옵니다.
실제로 register는 컴파일러에게 변수가 프로그램의 다른 어떤 것과도 별명을 가지지 않는다는 것을 알려줍니다 (char조차 포함하지 않음).
그것은 다양한 상황에서 현대의 컴파일러에 의해 이용 될 수 있으며 복잡한 코드에서 컴파일러를 상당히 도울 수 있습니다. 간단한 코드에서는 컴파일러가 스스로 알아낼 수 있습니다.
그렇지 않으면 목적이 없으며 레지스터 할당에 사용되지 않습니다. 컴파일러가 최신 버전 인 한 일반적으로 성능을 저하시키지 않습니다.
register
주소를 사용하는 것을 금지합니다. 그렇지 않으면 컴파일러가 변수의 주소가 외부 함수에 전달 되었지만 컴파일러가 레지스터 최적화를 적용 할 수있는 경우를 알려주는 것이 유용 할 수 있습니다 (변수는 해당 특정 호출에 대해 메모리로 플러시 되지만 함수가 반환되면 컴파일러는이를 주소를 가져 오지 않은 변수로 다시 처리 할 수 있습니다).
bar
A는 register
변수, 컴파일러는 자사의 여가 수도 교체 foo(&bar);
와 함께 int temp=bar; foo(&temp); bar=temp;
,하지만의 주소를 복용 bar
지나치게 복잡한 규칙처럼 보이지 않을 것입니다 다른 대부분의 상황에서 금지 될 것입니다. 만약 변수가 레지스터에 유지 될 수 있다면, 대체는 코드를 더 작게 만들 것입니다. 어쨌든 변수를 RAM에 보관 해야하는 경우 대체하면 코드가 더 커집니다. 컴파일러로 대체 할 것인지에 대한 의문을 남기면 두 경우 모두 더 나은 코드로 이어질 것입니다.
register
컴파일러가 주소를 가져올 수 있는지 여부에 관계없이 전역 변수에 대한 자격을 허용하면 전역 변수를 사용하는 인라인 함수가 루프에서 반복적으로 호출되는 경우 멋진 최적화가 가능합니다. 루프 반복 사이에 레지스터에 변수를 유지하는 다른 방법을 생각할 수 없습니다.
최적화에 사용되었지만 표준에 명확하게 정의되어 있지 않다는 것을 읽었습니다.
사실 그것은 된다 명확하게 C 표준에 의해 정의. N1570 초안 섹션 6.7.1 단락 6 인용 (다른 버전은 동일한 문구가 있음) :
스토리지 클래스 지정자가있는 오브젝트의 식별자 선언은 오브젝트에
register
대한 액세스가 가능한 한 빠르다는 것을 나타냅니다. 이러한 제안이 효과적인 범위는 구현에 따라 다릅니다.
단항 &
연산자는로 정의 된 객체에 적용되지 않을 수 register
및 register
외부 선언에서 사용될 수 없다.
register
정규화 된 개체에 고유 한 다른 몇 가지 (정확하게 모호한) 규칙이 있습니다.
register
동작이 정의되지 않습니다. register
이지만 그러한 객체로 유용한 것을 수행 할 수는 없습니다 (배열에 색인을 생성하려면 초기 요소의 주소를 사용해야합니다)._Alignas
(C11 신규) 지시자는 그러한 객체에 적용 할 수 없다.va_start
매크로에 전달 된 매개 변수 이름 이 register
-qualified이면 동작이 정의되지 않습니다.다른 몇 가지가있을 수 있습니다. 표준 초안을 다운로드하고 관심이 있다면 "등록"을 검색하십시오.
이름에서 알 수 있듯이 원래 의미 register
는 객체를 CPU 레지스터에 저장해야 한다는 것입니다 . 그러나 컴파일러 최적화가 개선되면서 유용성이 떨어졌습니다. C 표준의 최신 버전은 CPU 레지스터를 더 이상 참조하지 않습니다. 더 이상 필요하지 않다고 가정하기 때문입니다 (레지스터를 사용하지 않는 아키텍처가 있음). 일반적 register
으로 객체 선언에 적용 하면 생성 된 코드가 컴파일러의 레지스터 할당을 방해하기 때문에 생성 된 코드 가 더 나빠질 수 있습니다 . 여전히 유용한 몇 가지 경우가있을 수 있습니다 (예 : 변수에 액세스하는 빈도를 실제로 알고 있고 최신 최적화 컴파일러가 알아낼 수있는 것보다 지식이 더 나은 경우).
주요 유형의 효과 register
는 객체의 주소를 가져 오려는 시도를 방지한다는 것입니다. 이것은 로컬 변수에만 적용될 수 있고 최적화 컴파일러는 이러한 객체의 주소가 취해지지 않았 음을 스스로 확인할 수 있기 때문에 최적화 힌트로 특히 유용하지 않습니다.
register
당신이 생각하고있는 경우-한정 배열 객체 가 없습니다 .
register
키워드는 그러한 변수의 주소를 사용하는 것이 합법적이지만 주소를 가져올 때 변수를 임시로 복사하고 임시에서 다시로드하여 의미에 영향을 미치지 않는 경우에만 유용한 목적을 제공 할 수 있습니다 다음 순서 지점에서. 그러면 컴파일러는 변수가 주소가 취해지는 곳에서 플러시되는 경우 모든 포인터 액세스의 레지스터에 변수가 안전하게 유지 될 수 있다고 가정 할 수 있습니다.
이야기 시간!
언어 인 C는 컴퓨터의 추상화입니다. 컴퓨터가하는 일, 즉 메모리 조작, 수학, 인쇄 등의 일을 할 수 있습니다.
그러나 C는 추상화 일뿐입니다. 그리고 궁극적으로, 당신 에게서 추출되는 것은 어셈블리 언어입니다. 어셈블리는 CPU가 읽는 언어이며, 사용하는 경우 CPU 측면에서 작업을 수행합니다. CPU는 무엇을합니까? 기본적으로 메모리에서 읽고 수학하고 메모리에 씁니다. CPU는 메모리의 숫자에 대해서만 수학을 수행하지 않습니다. 먼저, 레지스터 라고하는 CPU 내부의 메모리에서 메모리로 숫자를 이동해야합니다.. 이 번호에 필요한 작업을 모두 마치면 정상적인 시스템 메모리로 다시 옮길 수 있습니다. 왜 시스템 메모리를 사용합니까? 레지스터 수는 제한되어 있습니다. 최신 프로세서에서는 약 100 바이트 만 사용할 수 있으며 이전의 인기있는 프로세서는 훨씬 환상적으로 제한되었습니다 (6502에는 3 개의 8 비트 레지스터가 무료로 사용되었습니다). 따라서 일반적인 수학 연산은 다음과 같습니다.
load first number from memory
load second number from memory
add the two
store answer into memory
많은 것이 ... 수학이 아닙니다. 이러한로드 및 저장 작업은 처리 시간의 최대 절반이 걸릴 수 있습니다. C는 컴퓨터의 추상화이므로 프로그래머가 레지스터 사용 및 저글링에 대한 걱정을 덜어 주었으며, 컴퓨터 수와 유형이 컴퓨터마다 다르기 때문에 C는 컴파일러에 대해서만 레지스터 할당을 담당합니다. 한 가지 예외가 있습니다.
변수를 선언 할 때 register
컴파일러에게 "이 변수를 많이 사용하거나 짧게 사용하려고합니다. 내가 당신이라면 레지스터에 유지하려고 노력할 것입니다." C 표준이 컴파일러가 실제로 아무것도 할 필요가 없다고 말하면 C 표준은 어떤 컴퓨터를 컴파일하는지 알지 못하기 때문에 위의 6502와 같을 수 있습니다. , 전화 번호를 유지하기위한 예비 레지스터가 없습니다. 그러나 주소를 가질 수 없다는 메시지는 레지스터에 주소가 없기 때문입니다. 그들은 프로세서의 손입니다. 컴파일러는 주소를 제공 할 필요가 없으며, 주소를 전혀 가질 수 없기 때문에 이제 컴파일러에 몇 가지 최적화가 열려 있습니다. 즉, 항상 레지스터에 번호를 유지할 수 있습니다. 그렇지 않습니다 컴퓨터 메모리에 저장된 위치에 대해 걱정할 필요가 없습니다 (다시 다시 가져와야 함). 다른 변수에 펑크를 걸거나 다른 프로세서에 변경하거나 위치 변경 등을 할 수도 있습니다.
tl; dr : 많은 수학을 수행하는 수명이 짧은 변수. 한 번에 너무 많이 선언하지 마십시오.
컴파일러의 정교한 그래프 채색 알고리즘이 엉망입니다. 이것은 레지스터 할당에 사용됩니다. 글쎄요. 컴파일러에 대한 힌트 역할을합니다. 그러나 레지스터 변수의 주소를 사용할 수 없으므로 완전히 무시하지 마십시오 (이제 컴파일러는 다르게 행동하려고 시도합니다). 어떤 식 으로든 사용하지 말라고 말하고 있습니다.
키워드는 오래 전부터 사용되었습니다. 집게 손가락으로 모두 계산할 수있는 레지스터가 너무 적을 때.
그러나 내가 말했듯이 더 이상 사용되지 않는다고해서 사용할 수 없다는 의미는 아닙니다.
그냥 비교 (모든 실제 목적없이) 약간의 데모는 다음 제거 할 때 register
각 변수 전에 키워드를,이 코드 조각은 내 i7의 (GCC)에 3.41 초 정도 걸립니다 으로 register
0.7 초에서 동일한 코드 완료.
#include <stdio.h>
int main(int argc, char** argv) {
register int numIterations = 20000;
register int i=0;
unsigned long val=0;
for (i; i<numIterations+1; i++)
{
register int j=0;
for (j;j<i;j++)
{
val=j+i;
}
}
printf("%d", val);
return 0;
}
다음 코드를 사용하여 QNX 6.5.0에서 레지스터 키워드를 테스트했습니다.
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int main(int argc, char *argv[]) {
uint64_t cps, cycle1, cycle2, ncycles;
double sec;
register int a=0, b = 1, c = 3, i;
cycle1 = ClockCycles();
for(i = 0; i < 100000000; i++)
a = ((a + b + c) * c) / 2;
cycle2 = ClockCycles();
ncycles = cycle2 - cycle1;
printf("%lld cycles elapsed\n", ncycles);
cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
printf("This system has %lld cycles per second\n", cps);
sec = (double)ncycles/cps;
printf("The cycles in seconds is %f\n", sec);
return EXIT_SUCCESS;
}
나는 다음과 같은 결과를 얻었다 :
-> 807679611 사이클 경과
->이 시스템의 초당주기는 3300830000입니다.
-> 초 단위의 사이클은 ~ 0.244600입니다.
그리고 이제 레지스터 int없이 :
int a=0, b = 1, c = 3, i;
나는 얻었다 :
-> 1421694077주기 경과
->이 시스템의 초당주기는 3300830000입니다.
-> 초 단위의 사이클은 ~ 0.430700입니다.
레지스터는 컴파일러에게이 변수가 변수 사용에 사용할 수있는 소수의 레지스터 중 하나에 저장을 정당화 할만큼 충분히 쓰기 / 읽기를했다고 컴파일러에 알립니다. 레지스터에서 읽기 / 쓰기가 일반적으로 빠르며 더 작은 op 코드 세트가 필요할 수 있습니다.
요즘에는 대부분의 컴파일러 최적화 프로그램이 해당 변수에 레지스터를 사용해야하는지 여부와 시간을 결정하는 것보다 낫기 때문에 유용하지 않습니다.
70 년대에 C 언어가 시작될 때 프로그래머가 컴파일러에 힌트를 제공하여 변수가 매우 자주 사용될 것임을 알리고 레지스터를 현명하게 사용해야한다는 것을 알려주기 위해 register 키워드가 도입되었습니다. 프로세서의 내부 레지스터 중 하나에 값을 유지하십시오.
오늘날 옵티마이 저는 프로그래머보다 레지스터에 유지 될 가능성이 높은 변수를 결정하는 데 훨씬 효율적이며 옵티마이 저는 항상 프로그래머의 힌트를 고려하지 않습니다.
많은 사람들이 register 키워드를 사용하지 말 것을 잘못 권장합니다.
이유를 보자!
register 키워드는 관련 부작용이 있습니다. 레지스터 유형 변수를 참조 할 수 없습니다.
다른 사람들에게 레지스터를 사용하지 말라고 조언하는 사람들은 이것을 추가적인 주장으로 잘못 생각합니다.
그러나 레지스터 변수의 주소를 사용할 수 없다는 사실을 알면 컴파일러 (및 최적화 프로그램)가이 변수의 값을 포인터를 통해 간접적으로 수정할 수 없음을 알 수 있습니다.
명령어 스트림의 특정 지점에서 레지스터 변수에 프로세서 레지스터에 값이 할당되어 있고 다른 변수의 값을 얻기 위해 레지스터를 사용하지 않은 경우 컴파일러는 다시로드 할 필요가 없다는 것을 알고 있습니다. 해당 레지스터의 변수 값 이를 통해 값 비싼 쓸모없는 메모리 액세스를 피할 수 있습니다.
자체 테스트를 수행하면 가장 내부 루프에서 성능이 크게 향상됩니다.
최적화 플래그를 사용하지 않고 gcc 9.3 asm 출력 (이 답변의 모든 것은 최적화 플래그가없는 표준 컴파일을 나타냅니다) :
#include <stdio.h>
int main(void) {
int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 3
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
#include <stdio.h>
int main(void) {
register int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
mov ebx, 3
add ebx, 1
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add rsp, 8
pop rbx
pop rbp
ret
이는 ebx
계산에 강제 로 사용됩니다. 즉, 수신자가 저장되기 때문에 스택으로 푸시하고 함수 끝에서 복원해야합니다. register
코드 1 개 메모리 쓰기 1 개 메모리 읽기 더 라인을 생산 (현실적으로이 0 R에 최적화 된 수 있지만 / 계산이에서 수행 된 경우 WS esi
의 C ++를 사용하여 발생하는 것입니다, const register
). 사용하지 않으면 register
쓰기 2 회와 읽기 1 회가 발생합니다 (읽기시 저장소로드로드가 발생하더라도). 이는 주소 (포인터)가 올바른 값을 읽을 수 있도록 스택에 직접 값이 존재하고 업데이트되어야하기 때문입니다. register
이 요구 사항이 없으며 지적 할 수 없습니다. const
그리고 register
기본적으로의 반대 volatile
및 사용volatile
파일 및 블록 범위의 const 최적화와 register
block-scope 의 최적화를 무시합니다 . const register
및 register
CONST 블록 범위 내에서 C에 아무런 역할을하지 않기 때문에, 동일한 출력을 생성 할 정도로 만 register
최적화가 적용된다.
clang에서는 register
무시되지만 const
최적화는 여전히 발생합니다.