Kapan kata kunci register sebenarnya berguna di C?

10

Saya bingung tentang penggunaan registerkata kunci dalam C. Secara umum dikatakan bahwa penggunaannya tidak diperlukan seperti dalam pertanyaan ini pada stackoverflow .

Apakah kata kunci ini sepenuhnya berlebihan dalam C karena kompiler modern atau apakah ada situasi yang masih dapat berguna? Jika ya, situasi apa yang membuat penggunaan registerkata kunci bermanfaat?

Aseem Bansal
sumber
4
Saya pikir pertanyaan terkait dan jawabannya sama seperti yang Anda harapkan di sini. Jadi tidak akan ada informasi baru yang bisa Anda dapatkan di sini.
Uwe Plonus
@UwePlonus Saya memikirkan hal yang sama tentang constkata kunci tetapi pertanyaan ini membuktikan bahwa saya salah. Jadi saya akan menunggu dan melihat apa yang saya dapatkan.
Aseem Bansal
Saya pikir constkata kunci adalah sesuatu yang berbeda dengan register.
Uwe Plonus
4
Ini berguna jika Anda secara tidak sengaja kembali ke masa lalu dan terpaksa menggunakan salah satu dari kompiler C awal. Selain itu itu tidak berguna sama sekali, sudah sepenuhnya usang selama bertahun-tahun.
JohnB
@UwePlonus Saya hanya bermaksud bahwa mungkin ada skenario yang tidak diketahui bagi saya di mana kata kunci mungkin berguna.
Aseem Bansal

Jawaban:

11

Ini tidak berlebihan dalam hal bahasa, hanya saja dengan menggunakannya, Anda memberi tahu kompiler, Anda akan "lebih suka" memiliki variabel yang disimpan dalam register. Namun ada jaminan nol mutlak bahwa ini benar-benar akan terjadi selama runtime.

Jas
sumber
9
Lebih dari itu, hampir selalu merupakan kasus yang diketahui oleh kompiler dan Anda membuang-buang napas
Daniel Gratzer
6
@ Jozefg: lebih buruk lagi. Anda menjalankan risiko bahwa kompiler menghormati permintaan / petunjuk Anda dan menghasilkan kode yang lebih buruk sebagai hasilnya.
Bart van Ingen Schenau
9

Seperti yang telah disebutkan, pengoptimal kompiler pada dasarnya membuat registerkata kunci usang untuk tujuan lain selain mencegah alias. Namun, ada seluruh basis kode yang dikompilasi dengan optimasi yang dimatikan ( -O0dalam bahasa gcc ). Untuk kode seperti itu, registerkata kunci dapat memiliki efek luar biasa. Khususnya, variabel yang sebaliknya akan mendapatkan slot di tumpukan (yaitu semua parameter fungsi dan variabel otomatis) dapat ditempatkan langsung ke dalam register jika dideklarasikan dengan registerkata kunci.

Berikut ini adalah contoh dunia nyata: asumsikan bahwa beberapa pencarian basis data telah terjadi dan bahwa kode pencarian telah memasukkan tuple yang diambil ke dalam C struct. Lebih jauh, asumsikan bahwa beberapa subset dari struct C ini perlu disalin ke struct lain — mungkin struct kedua ini adalah catatan cache yang mewakili metadata yang disimpan dalam database itu, karena kendala memori hanya cache subset dari setiap catatan metadata yang disimpan dalam database.

Diberikan fungsi yang mengambil pointer ke setiap tipe struct dan yang tugas utamanya adalah menyalin beberapa anggota dari struct awal ke struct kedua: variabel pointer struct akan hidup di stack. Ketika penugasan terjadi dari satu anggota struct ke yang lain, alamat struct akan, untuk setiap penugasan, dimasukkan ke dalam register untuk melakukan akses anggota struct yang sedang disalin. Jika pointer struct harus dinyatakan dengan registerkata kunci, alamat struct akan tetap ada di register, secara efektif memotong instruksi load-address-to-register untuk setiap penugasan.

Sekali lagi, ingat bahwa uraian di atas berlaku untuk kode yang tidak dioptimalkan .

jefe2000
sumber
6

Pada dasarnya Anda memberi tahu kompiler bahwa Anda tidak akan mengambil alamat variabel dan kompiler kemudian dapat melakukan optimasi lebih lanjut. Sejauh yang saya tahu, kompiler modern cukup mampu untuk menentukan apakah suatu variabel dapat / harus disimpan dalam register atau tidak.

Contoh:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 
Lucas
sumber
Dereferensi atau ambil alamat?
detly
@detly: Anda tentu saja benar
Lucas
0

Pada hari-hari komputer 16-bit, seseorang sering membutuhkan banyak register untuk menjalankan 32-bit perkalian dan pembagian. Ketika unit floating point dimasukkan ke dalam chip dan kemudian arsitektur 64-bit 'mengambil alih', baik lebar register dan jumlah mereka diperluas. Ini pada akhirnya mengarah pada pembuatan kembali CPU yang lengkap. Lihat Mendaftar File di Wikipedia.

Singkatnya, ini akan membawa Anda sedikit waktu untuk mencari tahu apa yang sebenarnya terjadi jika Anda menggunakan chip X86 atau ARM 64-bit. Jika Anda menggunakan CPU tertanam 16-bit, ini mungkin membuat Anda mendapatkan sesuatu. Namun, sebagian besar chip tertanam tidak menjalankan waktu kritis - oven microwave Anda mungkin mengambil sampel touchpad Anda 10.000 kali per detik - tidak ada yang menyusahkan CPU 4Mhz.

Meredith Poor
sumber
1
4 MIPS / 10.000 poling / detik = 400 instruksi / poling. Margin yang hampir tidak sebanyak yang Anda inginkan. Juga perhatikan bahwa beberapa prosesor 4 MHz di-mikrocod secara internal, artinya mereka tidak mendekati 1 MIP / MHz.
John R. Strohm
@ JohnR.Strohm - Mungkin ada situasi di mana seseorang dapat membenarkan mencari tahu persis berapa banyak siklus instruksi yang akan diambil, tetapi sering kali jalan keluar yang lebih murah sekarang adalah dengan hanya mendapatkan chip yang lebih cepat dan mengeluarkan produk dari pintu. Dalam contoh yang diberikan, tentu saja, seseorang tidak harus terus sampel di 10.000 jika seseorang memiliki perintah - itu mungkin tidak melanjutkan pengambilan sampel selama seperempat detik tanpa ada kerusakan yang dilakukan. Menjadi semakin sulit untuk mencari tahu di mana optimalisasi yang diarahkan programmer akan menjadi masalah.
Meredith Poor
1
Tidak selalu mungkin untuk "hanya mendapatkan chip yang lebih cepat dan mengeluarkan produk". Pertimbangkan pemrosesan gambar waktu nyata. 640x480 piksel / bingkai x 60 bingkai / detik x N instruksi per piksel ditambahkan dengan cepat. (Pelajaran dari pemrosesan gambar real-time adalah bahwa Anda berkeringat darah di atas kernel piksel Anda dan Anda hampir mengabaikan semua yang lain, karena itu berjalan sekali per baris atau sekali per patch atau sekali per frame, yang bertentangan dengan ratusan kali per baris atau menambal atau puluhan atau ratusan ribu kali per frame.)
John R. Strohm
@ JohnR.Strohm - mengambil contoh pemrosesan gambar real-time, saya akan menganggap lingkungan minimum adalah 32 bit. Keluar mengambil risiko (karena saya tidak tahu seberapa praktis ini dilakukan) banyak akselerator grafis yang dibangun ke dalam chip juga dapat digunakan untuk pengenalan gambar, jadi chip ARM (misalnya) yang memiliki mesin render terintegrasi mungkin memiliki ALU tambahan yang dapat digunakan untuk pengakuan. Pada saat itu penggunaan kata kunci 'daftar' untuk optimasi adalah bagian kecil dari masalah.
Meredith Poor
-3

Untuk memastikan apakah kata kunci register memiliki signifikansi, kode contoh kecil tidak akan berfungsi. Berikut adalah kode-C yang menunjukkan kepada saya, kata kunci register masih memiliki signifikansi. Tapi mungkin berbeda dengan GCC di Linux, saya tidak tahu. AKAN register int k & l disimpan dalam register CPU atau tidak? Pengguna Linux (terutama) harus mengkompilasi dengan GCC dan optimisasi. Dengan Borland bcc32 kata kunci daftar tampaknya berfungsi (dalam contoh ini), karena & -operator memberikan kode kesalahan untuk register yang dinyatakan bilangan bulat. CATATAN! Ini BUKAN kasus dengan contoh kecil dengan Borland pada Windows! Untuk benar-benar melihat apa yang dioptimalkan oleh kompiler atau tidak, itu harus menjadi lebih dari contoh kecil. Loop kosong tidak akan berhasil! Namun demikian - JIKA alamat BISA dibaca dengan & -operator, variabel tidak disimpan dalam register CPU. Tetapi jika sebuah register menyatakan variabel tidak dapat dibaca (menyebabkan kode kesalahan saat kompilasi) - Saya harus berasumsi bahwa kata kunci register benar-benar memasukkan variabel ke dalam register CPU. Mungkin berbeda di berbagai platform, saya tidak tahu. (Jika berhasil, jumlah "kutu" akan jauh lebih rendah dengan deklarasi register.

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

} 
John P Eriksson
sumber
Akan ada pembagian dengan nol di atas, silakan ubah {tmp + = ii / jj;} menjadi {tmp + = jj / ii;} - benar-benar minta maaf untuk ini
John P Eriksson
Biarkan k dan saya mulai dengan 1 - bukan nol. Sangat menyesal.
John P Eriksson
3
Anda dapat mengedit jawaban Anda alih-alih menulis koreksi dalam komentar.
Jan Doggen