레지스터 키워드는 C에서 실제로 언제 유용합니까?


10

registerC에서 키워드 사용에 대해 혼란 스럽 습니다. 일반적으로 stackoverflow 의이 질문에서와 같이 키워드를 사용할 필요가 없다고 들었습니다 .

이 키워드는 최신 컴파일러로 인해 C에서 완전히 중복되거나 여전히 유용한 상황이 있습니까? 그렇다면 register키워드 사용 이 실제로 도움이 되는 상황은 무엇 입니까?


4
나는 연결된 질문과 그에 대한 답변이 여기에서 기대할 수있는 것과 같다고 생각합니다. 따라서 여기에 얻을 수있는 새로운 정보는 없습니다.
Uwe Plonus

@ UwePlonus const키워드 에 대해 동일하게 생각 했지만 이 질문 은 내가 틀렸다는 것을 증명했습니다. 그래서 기다린 후 내가 얻는 것을 볼 수 있습니다.
Aseem Bansal

const키워드가 레지스터와 다른 것으로 생각합니다 .
Uwe Plonus

4
실수로 시간을 거슬러 올라가 초기 C 컴파일러 중 하나를 사용해야하는 경우에 유용합니다. 그다지 유용하지는 않지만 수년 동안 완전히 사용되지 않았습니다.
JohnB

@UwePlonus 나는 키워드가 유용 할 수있는 시나리오가 나에게 알려지지 않았 음을 의미했습니다.
Aseem Bansal

답변:


11

언어 측면에서 중복되지는 않습니다. 단지 그것을 사용함으로써 컴파일러에게 변수를 레지스터에 저장하는 것을 "선호"하는 것입니다. 그러나 이것이 실제로 런타임 중에 발생한다는 보장은 전혀 없습니다.


9
그보다 컴파일러가 가장 잘 알고 숨을 낭비하는 경우는 거의 항상 그런 경우입니다.
Daniel Gratzer

6
@ jozefg : 더 나빠. 컴파일러가 요청 / 힌트를 존중 하고 결과적으로 더 나쁜 코드를 생성 할 위험이 있습니다.
Bart van Ingen Schenau

9

이미 언급했듯이 컴파일러 최적화 프로그램은 기본적으로 register앨리어싱 방지 이외의 목적으로 키워드를 사용하지 않습니다. 그러나, 최적화 (해제로 컴파일하는 전체 코드베이스가 -O0에서 GCC 발언은 ). 이러한 코드의 경우 register키워드가 큰 영향을 줄 수 있습니다. 특히 스택에서 슬롯을 얻는 변수 (예 : 모든 함수 매개 변수 및 자동 변수) register 키워드로 선언 된 경우 레지스터에 직접 배치 될 수 있습니다 .

실제 예는 다음과 같습니다. 일부 데이터베이스 검색이 발생했으며 검색 코드가 검색된 튜플을 C 구조체에 넣었다고 가정합니다. 또한이 C 구조체의 일부 하위 집합을 다른 구조체에 복사해야한다고 가정합니다.이 두 번째 구조체는 데이터베이스에 저장된 메타 데이터를 나타내는 캐시 레코드 일 수 있습니다. 메모리 제약으로 인해 저장된 메타 데이터 레코드의 하위 세트 만 캐시됩니다. 데이터베이스에서.

각 구조체 유형에 대한 포인터를 가져오고 초기 구조체에서 두 번째 구조체로 일부 멤버를 복사하는 것이 유일한 역할을하는 함수가 주어지면 구조체 포인터 변수가 스택에 있습니다. 한 구조체의 멤버에서 다른 구조체의 멤버로의 할당이 발생함에 따라, 복사되는 구조체의 멤버에 대한 액세스를 수행하기 위해 각 할당에 대해 구조체의 주소가 레지스터에로드됩니다. register키워드 로 구조체 포인터를 선언 하면 구조체의 주소가 레지스터에 남아있게되어 각 할당에 대한로드 주소-레지스터 등록 명령이 효과적으로 제거됩니다.

다시 말하지만 위의 설명은 최적화되지 않은 코드에 적용됩니다 .


6

기본적으로 컴파일러에게 변수의 주소를 사용하지 않으며 컴파일러는 표면적으로 추가 최적화를 수행 할 수 있다고 말합니다. 내가 아는 한, 현대 컴파일러는 변수를 레지스터에 유지할 수 있는지 여부를 결정할 수 있습니다.

예:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 

역 참조 또는 주소?
detly

@detly : 물론 맞습니다
Lucas

0

16 비트 컴퓨터 시대에는 32 비트 곱셈과 나눗셈을 실행하기 위해 종종 여러 개의 레지스터가 필요했습니다. 부동 소수점 단위가 칩에 통합 된 다음 64 비트 아키텍처가 '인계'됨에 따라 레지스터 폭과 개수가 모두 확장되었습니다. 이것은 결국 CPU의 완전한 재구성을 초래합니다. Wikipedia에 파일 등록을 참조하십시오 .

간단히 말해, 64 비트 X86 또는 ARM 칩을 사용하는 경우 실제로 진행중인 작업을 파악하는 데 약간의 시간이 걸립니다. 16 비트 임베디드 CPU를 사용하는 경우 실제로 무언가를 얻을 수 있습니다. 그러나 대부분의 소형 임베디드 칩은 시간이 중요하지 않습니다. 전자 레인지는 터치 패드를 초당 10,000 번 샘플링 할 수 있습니다. 4Mhz CPU에 부담을주지 않습니다.


1
4 MIPS / 10,000 폴 / 초 = 400 명령 / 폴. 그것은 당신이 원하는만큼 거의 여백이 아닙니다. 또한 4MHz 프로세서가 내부적으로 마이크로 코딩되어 거의 1MIP / MHz 근처에 있지 않다는 점에 유의하십시오.
John R. Strohm

@ JohnR.Strohm-얼마나 많은 명령 사이클을 수행해야하는지 정확히 파악할 수있는 상황이있을 수 있지만, 더 저렴한 방법은 더 빠른 칩을 얻고 제품을 출시하는 것입니다. 물론 주어진 예에서, 명령이있는 경우 10,000 개로 샘플링을 계속할 필요는 없습니다. 아무런 피해없이 1/4 초 동안 샘플링을 다시 시작하지 못할 수도 있습니다. 프로그래머 지향 최적화가 중요한 부분을 파악하는 것이 점점 어려워지고 있습니다.
메러디스 가난한

1
"더 빠른 칩을 얻고 제품을 문 밖으로 내보내는 것"이 ​​항상 가능한 것은 아닙니다. 실시간 이미지 처리를 고려하십시오. 픽셀 당 640x480 픽셀 / 프레임 x 60 프레임 / 초 x N 명령어가 빠르게 추가됩니다. (실시간 이미지 처리의 교훈은 픽셀 커널에 피가 땀을 흘리고 다른 라인을 무시한다는 것입니다. 라인 당 한 번 또는 패치 당 한 번 또는 프레임 당 한 번 실행되기 때문에 라인 당 수백 번이나 프레임 당 패치 또는 수십만 번.)
John R. Strohm

@ JohnR.Strohm-실시간 이미지 처리 예제를 고려할 때 최소 환경은 32 비트라고 가정합니다. 칩에 내장 된 많은 그래픽 가속기가 이미지 인식에 사용될 수도 있으므로 렌더링 엔진이 통합 된 ARM 칩 (예 :)에 추가 ALU를 사용할 수 있습니다. 인정을 위해. 그때까지 최적화에 'register'키워드를 사용하는 것은 문제의 작은 부분입니다.
Meredith Poor

-3

register 키워드에 의미가 있는지 여부를 확인하기 위해 작은 예제 코드는 그렇지 않습니다. 여기에 나에게 제안하는 c 코드가 있는데 레지스터 키워드는 여전히 의미가 있습니다. 그러나 Linux의 GCC와는 다를 수 있습니다. 레지스터 int k & l은 CPU 레지스터에 저장됩니까? Linux 사용자는 (특히) GCC 및 최적화로 컴파일해야합니다. & 연산자가 레지스터 선언 정수에 대한 오류 코드를 제공하므로 Borland bcc32를 사용하면 레지스터 키워드가 작동하는 것처럼 보입니다 (이 예에서는). 노트! Windows에서 Borland를 사용한 작은 예제는 그렇지 않습니다! 컴파일러가 최적화하는 것을 실제로 확인하려면 작은 예제 이상이어야합니다. 빈 루프는하지 않습니다! 그럼에도 불구하고 & 연산자로 주소를 읽을 수 있으면 변수는 CPU 레지스터에 저장되지 않습니다. 그러나 레지스터 선언 변수를 읽을 수없는 경우 (컴파일시 오류 코드를 유발 함)-레지스터 키워드가 실제로 변수를 CPU 레지스터에 넣는 것으로 가정해야합니다. 나는 다양한 플랫폼에서 다를 수 있습니다. (작동하면 "틱"의 수는 레지스터 선언으로 훨씬 적습니다.

/* reg_or_not.c */  

#include <stdio.h>
#include <time.h>
#include <stdlib> //not requiered for Linux
#define LAPSb 50
#define LAPS 50000
#define MAXb 50
#define MAX 50000


int main (void)
{
/* 20 ints and 2 register ints */   

register int k,l;
int a,aa,b,bb,c,cc,d,dd,e,ee,f,ff,g,gg,h,hh,i,ii,j,jj;


/* measure some ticks also */  

clock_t start_1,start_2; 
clock_t finish_1,finish_2;
long tmp; //just for the workload 


/* pointer declarations of all ints */

int *ap, *aap, *bp, *bbp, *cp, *ccp, *dp, *ddp, *ep, *eep;
int *fp, *ffp, *gp, *ggp, *hp, *hhp, *ip, *iip, *jp, *jjp;
int *kp,*lp;

/* end of declarations */
/* read memory addresses, if possible - which can't be done in a CPU-register */     

ap=&a; aap=&aa; bp=&b; bbp=&bb;
cp=&c; ccp=&cc; dp=&d; ddp=&dd;
ep=&e; eep=&ee; fp=&f; ffp=&ff;
gp=&g; ggp=&gg; hp=&h; hhp=&hh;
ip=&i; iip=&ii; jp=&j; jjp=&jj;

//kp=&k;  //won't compile if k is stored in a CPU register  
//lp=&l;  //same - but try both ways !


/* what address , isn't the issue in this case - but if stored in memory    some "crazy" number will be shown, whilst CPU-registers can't be read */

printf("Address a aa: %u     %u\n",a,aa);
printf("Address b bb: %u     %u\n",b,bb);
printf("Address c cc: %u     %u\n",c,cc);
printf("Address d dd: %u     %u\n",d,dd);
printf("Address e ee: %u     %u\n",e,ee);
printf("Address f ff: %u     %u\n",f,ff);
printf("Address g gg: %u     %u\n",g,gg);
printf("Address h hh: %u     %u\n",h,hh);
printf("Address i ii: %u     %u\n",i,ii);
printf("Address j jj: %u     %u\n\n",j,jj);

//printf("Address k:  %u \n",k); //no reason to try "k" actually is in a CPU-register 
//printf("Address l:  %u \n",l); 


start_2=clock(); //just for fun      

/* to ensure workload */
for (a=1;a<LAPSb;a++) {for (aa=0;aa<MAXb;aa++);{tmp+=aa/a;}}
for (b=1;b<LAPSb;b++) {for (bb=0;bb<MAXb;bb++);{tmp+=aa/a;}}
for (a=1;c<LAPSb;c++) {for (cc=0;cc<MAXb;cc++);{tmp+=bb/b;}}
for (d=1;d<LAPSb;d++) {for (dd=0;dd<MAXb;dd++);{tmp+=cc/c;}}
for (e=1;e<LAPSb;e++) {for (ee=0;ee<MAXb;ee++);{tmp+=dd/d;}}
for (f=1;f<LAPSb;f++) {for (ff=0;ff<MAXb;ff++);{tmp+=ee/e;}}
for (g=1;g<LAPSb;g++) {for (gg=0;gg<MAXb;gg++);{tmp+=ff/f;}}
for (h=1;h<LAPSb;h++) {for (hh=0;hh<MAXb;hh++);{tmp+=hh/h;}}
for (jj=1;jj<LAPSb;jj++) {for (ii=0;ii<MAXb;ii++);{tmp+=ii/jj;}}

start_1=clock(); //see following printf
for (i=0;i<LAPS;i++) {for (j=0;j<MAX;j++);{tmp+=j/i;}} /* same double   loop - in supposed memory */
finish_1=clock(); //see following printf

printf ("Memory: %ld ticks\n\n", finish_1 - start_1); //ticks for memory

start_1=clock(); //see following printf
for (k=0;k<LAPS;k++) {for (l=0;l<MAX;l++);{tmp+=l/k;}}  /* same double       loop - in supposed register*/
finish_1=clock(); //see following printf     

printf ("Register: %ld ticks\n\n", finish_1 - start_1); //ticks for CPU register (?) any difference ?   

finish_2=clock();

printf ("Total: %ld ticks\n\n", finish_2 - start_2); //really for fun only           

system("PAUSE"); //only requiered for Windows, so the CMD-window doesn't vanish     

return 0;

} 

제발 변화 위의 0으로 부서가있을 것이다 {TMP + = II / JJ;}에 {TMP + = JJ / II;} - 정말 죄송합니다 이것에 대한
존 P 에릭손

또한 k와 i가 0이 아닌 1로 시작하게하십시오. 매우 죄송합니다.
John P Eriksson

3
주석에 수정 사항을 작성하는 대신 답을 편집 할 수 있습니다.
Jan Doggen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.