Khi nào từ khóa đăng ký thực sự hữu ích trong C?


10

Tôi bối rối về việc sử dụng registertừ khóa trong C. Người ta thường nói rằng việc sử dụng nó không cần thiết như trong câu hỏi này trên stackoverflow .

Là từ khóa này hoàn toàn dư thừa trong C do trình biên dịch hiện đại hoặc có những tình huống mà nó vẫn có thể hữu ích? Nếu có, một số tình huống trong đó sử dụng registertừ khóa là thực sự hữu ích?


4
Tôi nghĩ rằng câu hỏi liên kết và câu trả lời cho nó giống như bạn có thể mong đợi ở đây. Vì vậy, sẽ không có thông tin mới mà bạn có thể nhận được ở đây.
Uwe Plonus

@UwePlonus Tôi cũng nghĩ như vậy về consttừ khóa nhưng câu hỏi này đã chứng minh rằng tôi đã sai. Vì vậy, tôi sẽ chờ xem tôi nhận được gì.
Aseem Bansal

Tôi nghĩ rằng consttừ khóa là một cái gì đó khác với đăng ký.
Uwe Plonus

4
Nó rất hữu ích nếu bạn vô tình quay ngược thời gian và buộc phải sử dụng một trong những trình biên dịch C đầu tiên. Ngoài ra, nó không hữu ích chút nào, nó đã hoàn toàn lỗi thời trong nhiều năm.
JohnB

@UwePlonus Tôi chỉ có nghĩa là có thể có những kịch bản mà tôi không biết trong đó một từ khóa có thể hữu ích.
Aseem Bansal

Câu trả lời:


11

Nó không dư thừa về mặt ngôn ngữ, chỉ là bằng cách sử dụng nó, bạn đang nói với trình biên dịch, bạn sẽ "thích" có một biến được lưu trữ trong thanh ghi. Tuy nhiên, hoàn toàn không có đảm bảo rằng điều này sẽ thực sự xảy ra trong thời gian chạy.


9
Hơn thế nữa, hầu như luôn luôn là trường hợp trình biên dịch biết rõ nhất và bạn đang lãng phí hơi thở của mình
Daniel Gratzer

6
@jozefg: thậm chí còn tệ hơn. Bạn gặp rủi ro khi trình biên dịch tôn trọng yêu cầu / gợi ý của bạn và kết quả là tạo ra mã xấu hơn .
Bart van Ingen Schenau 29/07/13

9

Như đã đề cập, tối ưu hóa trình biên dịch về cơ bản làm cho registertừ khóa bị lỗi thời cho các mục đích khác ngoài việc ngăn chặn răng cưa. Tuy nhiên, có toàn bộ các cơ sở mã được biên dịch với tối ưu hóa đã tắt ( -O0trong gcc-speak ). Đối với mã như vậy, registertừ khóa có thể có hiệu quả tuyệt vời. Cụ thể, các biến có thể có một vị trí trên ngăn xếp (nghĩa là tất cả các tham số chức năng và biến tự động) có thể được đặt trực tiếp vào một thanh ghi nếu được khai báo bằng registertừ khóa.

Đây là một ví dụ trong thế giới thực: giả sử rằng một số truy xuất cơ sở dữ liệu đã xảy ra và mã truy xuất đã nhét bộ dữ liệu đã truy xuất vào cấu trúc C. Hơn nữa, giả sử rằng một số tập hợp con của cấu trúc C này cần được sao chép sang một cấu trúc khác, có thể cấu trúc thứ hai này là một bản ghi bộ đệm đại diện cho siêu dữ liệu được lưu trữ trong cơ sở dữ liệu, do các ràng buộc bộ nhớ chỉ lưu trữ một tập hợp con của mỗi bản ghi siêu dữ liệu được lưu trữ trong cơ sở dữ liệu.

Đưa ra một hàm lấy một con trỏ tới từng loại cấu trúc và công việc duy nhất của nó là sao chép một số thành viên từ cấu trúc ban đầu sang cấu trúc thứ hai: các biến con trỏ cấu trúc sẽ sống trên ngăn xếp. Khi các nhiệm vụ xảy ra từ các thành viên của cấu trúc này sang các cấu trúc khác, các địa chỉ của cấu trúc, đối với mỗi nhiệm vụ, sẽ được tải vào một thanh ghi để thực hiện quyền truy cập của các thành viên của cấu trúc đang được sao chép. Nếu các con trỏ cấu trúc được khai báo bằng registertừ khóa, địa chỉ của các cấu trúc sẽ vẫn còn trong các thanh ghi, cắt bỏ hiệu quả các hướng dẫn tải địa chỉ vào đăng ký cho mỗi lần gán.

Một lần nữa, hãy nhớ rằng mô tả ở trên áp dụng cho mã không được tối ưu hóa .


6

Về cơ bản, bạn nói với trình biên dịch rằng bạn sẽ không lấy địa chỉ của biến và trình biên dịch sau đó có thể tối ưu hóa thêm. Theo như tôi biết, các trình biên dịch hiện đại có khả năng khá lớn để xác định xem một biến có thể / nên được giữ trong một thanh ghi hay không.

Thí dụ:

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

Dereference hoặc lấy địa chỉ của?
gièm pha

@detly: bạn tất nhiên là đúng
Lucas

0

Trong những ngày máy tính 16 bit, người ta thường cần nhiều thanh ghi để thực hiện các phép nhân và chia 32 bit. Khi các đơn vị dấu phẩy động được tích hợp vào các chip và sau đó các kiến ​​trúc 64 bit 'chiếm lấy', cả chiều rộng của các thanh ghi và số lượng chúng được mở rộng. Điều này cuối cùng dẫn đến việc tái kiến ​​trúc hoàn chỉnh CPU. Xem Đăng ký tập tin trên Wikipedia.

Nói tóm lại, bạn sẽ mất một chút thời gian để tìm hiểu điều gì đang thực sự xảy ra nếu bạn sử dụng chip X86 hoặc ARM 64 bit. Nếu bạn đang sử dụng CPU nhúng 16 bit, điều này thực sự có thể mang lại cho bạn thứ gì đó. Tuy nhiên, hầu hết các chip nhúng nhỏ không chạy bất kỳ thời gian quan trọng nào - lò vi sóng của bạn có thể lấy mẫu bàn di chuột của bạn 10.000 lần một giây - không có gì làm mất CPU 4Mhz.


1
4 MIPS / 10.000 cuộc thăm dò / giây = 400 hướng dẫn / cuộc thăm dò. Đó không phải là gần như nhiều như bạn muốn có. Cũng lưu ý rằng khá nhiều bộ xử lý 4 MHz đã được mã hóa bên trong, có nghĩa là chúng không ở gần 1 MIP / MHz.
John R. Strohm

@ JohnR.Strohm - Có thể có những tình huống mà người ta có thể biện minh để biết chính xác có bao nhiêu chu kỳ hướng dẫn sẽ thực hiện, nhưng thường thì cách rẻ hơn bây giờ là lấy chip nhanh hơn và đưa sản phẩm ra khỏi cửa. Trong ví dụ được đưa ra, tất nhiên, người ta không phải tiếp tục lấy mẫu ở mức 10.000 nếu có lệnh - nó có thể không tiếp tục lấy mẫu trong một phần tư giây mà không gây hại gì. Ngày càng trở nên khó khăn hơn để tìm ra nơi tối ưu hóa hướng lập trình viên sẽ là vấn đề.
Meredith Nghèo

1
Không phải lúc nào cũng có thể "chỉ cần lấy một con chip nhanh hơn và đưa sản phẩm ra khỏi cửa". Xem xét xử lý hình ảnh thời gian thực. Hướng dẫn 640x480 pixel / khung x 60 khung hình / giây x N trên mỗi pixel sẽ tăng nhanh. (Bài học từ xử lý hình ảnh thời gian thực là bạn đổ mồ hôi trên hạt nhân pixel của mình và bạn bỏ qua mọi thứ khác, bởi vì nó chạy một lần trên mỗi dòng hoặc một lần trên mỗi bản vá, trái ngược với hàng trăm lần trên mỗi dòng hoặc vá hoặc hàng chục hoặc hàng trăm ngàn lần trên mỗi khung.)
John R. Strohm

@ JohnR.Strohm - lấy ví dụ xử lý hình ảnh thời gian thực, tôi cho rằng môi trường tối thiểu là 32 bit. Đi ra ngoài chi (vì tôi không biết việc này thực tế đến mức nào), nhiều bộ tăng tốc đồ họa được tích hợp trong chip cũng có thể sử dụng để nhận dạng hình ảnh, vì vậy, chip ARM (ví dụ) có công cụ kết xuất tích hợp có thể có thêm ALU có thể sử dụng được để công nhận. Vào thời điểm đó, việc sử dụng từ khóa 'đăng ký' để tối ưu hóa là một phần nhỏ của vấn đề.
Meredith Nghèo

-3

Để thiết lập liệu từ khóa đăng ký có bất kỳ ý nghĩa nào hay không, các mã ví dụ nhỏ sẽ không làm. Đây là một mã c gợi ý cho tôi, từ khóa đăng ký vẫn có một ý nghĩa. Nhưng nó có thể khác với GCC trên Linux, tôi không biết. SILL đăng ký int k & l có được lưu trong thanh ghi CPU hay không? Người dùng Linux (đặc biệt) nên biên dịch với GCC và tối ưu hóa. Với Borland bcc32, từ khóa đăng ký dường như hoạt động (trong ví dụ này), vì & -operator cung cấp mã lỗi cho số nguyên khai báo đăng ký. GHI CHÚ! Đây không phải là trường hợp với một ví dụ nhỏ với Borland trên Windows! Để thực sự thấy những gì trình biên dịch tối ưu hóa hay không, nó phải là một ví dụ nhỏ hơn. Vòng lặp trống sẽ không làm! Tuy nhiên - NẾU địa chỉ CÓ THỂ được đọc với & -operator, biến không được lưu trong thanh ghi CPU. Nhưng nếu một biến được khai báo của thanh ghi không thể đọc được (gây ra mã lỗi khi biên dịch) - tôi phải giả định rằng từ khóa đăng ký thực sự đã đặt biến đó vào một thanh ghi CPU. Nó có thể khác nhau trên các nền tảng khác nhau, tôi không biết. (Nếu nó hoạt động, số lượng "tick" sẽ thấp hơn nhiều với khai báo đăng ký.

/* 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;

} 

Sẽ có một phân chia có số 0 ở trên, vui lòng thay đổi {tmp + = ii / jj;} thành {tmp + = jj / ii;} - thực sự xin lỗi vì điều này
John P Eriksson

Cũng để k và tôi bắt đầu bằng 1 - không phải không. Rất xin lỗi.
John P Eriksson

3
Bạn có thể chỉnh sửa câu trả lời của bạn thay vì viết các chỉnh sửa trong các bình luận.
Jan Doggen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.