¿Cuándo es realmente útil la palabra clave de registro en C?

10

Estoy confundido sobre el uso de la registerpalabra clave en C. Generalmente se dice que su uso no es necesario como en esta pregunta en stackoverflow .

¿Es esta palabra clave totalmente redundante en C debido a los compiladores modernos o hay situaciones en las que todavía puede ser útil? En caso afirmativo, ¿cuáles son algunas situaciones en las que el uso de registerpalabras clave es realmente útil?

Aseem Bansal
fuente
44
Creo que la pregunta vinculada y las respuestas son las mismas que puede esperar aquí. Por lo tanto, no habrá información nueva que pueda obtener aquí.
Uwe Plonus
@UwePlonus Pensé lo mismo sobre la constpalabra clave, pero esta pregunta demostró que estaba equivocado. Así que esperaré y veré qué obtengo.
Aseem Bansal
Creo que la constpalabra clave es algo diferente al registro.
Uwe Plonus
44
Es útil si retrocede accidentalmente en el tiempo y se ve obligado a usar uno de los primeros compiladores de C. Aparte de eso, no es útil en absoluto, ha sido completamente obsoleto durante años.
JohnB
@UwePlonus Solo quería decir que puede haber escenarios desconocidos para mí en los que una palabra clave podría ser útil.
Aseem Bansal

Respuestas:

11

No es redundante en términos de lenguaje, es solo que al usarlo, le está diciendo al compilador que "preferiría" tener una variable almacenada en el registro. Sin embargo, no hay absolutamente ninguna garantía de que esto suceda realmente durante el tiempo de ejecución.

Jas
fuente
99
Más que eso, casi siempre es el caso que el compilador sabe mejor y está perdiendo el aliento
Daniel Gratzer
66
@jozefg: aún peor. Corre el riesgo de que el compilador cumpla con su solicitud / sugerencia y produzca un código peor como resultado.
Bart van Ingen Schenau
9

Como ya se mencionó, los optimizadores del compilador esencialmente hacen que la registerpalabra clave quede obsoleta para otros propósitos que no sean el aliasing. Sin embargo, hay bases de código completas que se compilan con la optimización desactivada ( -O0en gcc-speak ). Para dicho código, la registerpalabra clave puede tener un gran efecto. Específicamente, las variables que de otro modo obtendrían un espacio en la pila (es decir, todos los parámetros de función y variables automáticas) se pueden colocar directamente en un registro si se declara con la registerpalabra clave.

Aquí hay un ejemplo del mundo real: suponga que se ha producido alguna recuperación de la base de datos y que el código de recuperación ha rellenado la tupla recuperada en una estructura C. Además, suponga que algún subconjunto de esta estructura C debe copiarse en otra estructura; tal vez esta segunda estructura es un registro de caché que representa los metadatos almacenados en la base de datos que, debido a restricciones de memoria, solo almacena en caché un subconjunto de cada registro de metadatos como está almacenado en la base de datos.

Dada una función que toma un puntero a cada tipo de estructura y cuyo único trabajo es copiar algunos miembros de la estructura inicial a la segunda estructura: las variables de puntero de estructura vivirán en la pila. A medida que las asignaciones ocurren de los miembros de una estructura a las de la otra, las direcciones de la estructura, para cada asignación, se cargarán en un registro para realizar el acceso de los miembros de la estructura que se están copiando. Si los punteros de estructura fueran declarados con la registerpalabra clave, las direcciones de las estructuras permanecerían en los registros, eliminando efectivamente las instrucciones de carga-dirección-en-registro para cada asignación.

Nuevamente, recuerde que la descripción anterior se aplica al código no optimizado .

jefe2000
fuente
6

Básicamente le dice al compilador que no tomará la dirección de la variable y el compilador puede hacer aparentemente más optimizaciones. Hasta donde sé, los compiladores modernos son bastante capaces de determinar si una variable puede / debe mantenerse en un registro o no.

Ejemplo:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 
Lucas
fuente
¿Desreferenciar o tomar la dirección de?
desviarse el
@detly: por supuesto que tienes razón
Lucas
0

En los días de la computadora de 16 bits, a menudo se necesitaban múltiples registros para ejecutar multiplicaciones y divisiones de 32 bits. A medida que las unidades de coma flotante se incorporaron a los chips y luego las arquitecturas de 64 bits 'se hicieron cargo', tanto el ancho de los registros como el número de ellos se expandieron. Esto eventualmente lleva a una rediseño completo de la CPU. Ver Registro de archivos en Wikipedia.

En resumen, le tomaría un poco de tiempo darse cuenta de lo que realmente está sucediendo si está en un chip X86 o ARM de 64 bits. Si está en una CPU incorporada de 16 bits, esto podría darle algo. Sin embargo, la mayoría de los chips integrados pequeños no funcionan con tiempo crítico: su horno de microondas podría estar tomando muestras de su panel táctil 10,000 veces por segundo, nada que agote una CPU de 4Mhz.

Meredith Pobre
fuente
1
4 MIPS / 10,000 encuestas / seg = 400 instrucciones / encuesta. Eso no es tanto margen como le gustaría tener. También tenga en cuenta que algunos procesadores de 4 MHz fueron microcodificados internamente, lo que significa que no estaban cerca de 1 MIP / MHz.
John R. Strohm
@ JohnR.Strohm: puede haber situaciones en las que uno podría justificar averiguar exactamente cuántos ciclos de instrucción va a tomar, pero a menudo la salida más barata ahora es obtener un chip más rápido y sacar el producto por la puerta. En el ejemplo dado, por supuesto, uno no tiene que continuar muestreando a 10,000 si tiene un comando; es posible que no reanude el muestreo durante un cuarto de segundo sin causar daño. Cada vez es más difícil determinar dónde va a importar la optimización dirigida por el programador.
Meredith Poor
1
No siempre es posible "obtener un chip más rápido y sacar el producto por la puerta". Considere el procesamiento de imágenes en tiempo real. 640x480 píxeles / cuadro x 60 cuadros / segundo x N las instrucciones por píxel se agregan rápidamente. (La lección del procesamiento de imágenes en tiempo real es que sudas sangre sobre tus núcleos de píxeles y casi ignoras todo lo demás, porque se ejecuta una vez por línea o una vez por parche o una vez por fotograma, en lugar de cientos de veces por línea o parche o decenas o cientos de miles de veces por fotograma.)
John R. Strohm
@ JohnR.Strohm: tomando el ejemplo de procesamiento de imágenes en tiempo real, supongo que el entorno mínimo es de 32 bits. Salir de apuros (porque no sé lo práctico que es hacer esto), muchos aceleradores gráficos integrados en chips también pueden usarse para el reconocimiento de imágenes, por lo que los chips ARM (por ejemplo) que tienen motores de renderizado integrados pueden tener ALU adicionales utilizables para el reconocimiento En ese momento, el uso de la palabra clave 'registrarse' para la optimización es una pequeña parte del problema.
Meredith Poor
-3

Para establecer si la palabra clave de registro tiene algún significado, los códigos de ejemplo pequeños no funcionarán. Aquí hay un código c que me sugiere, la palabra clave de registro todavía tiene un significado. Pero podría ser diferente con GCC en Linux, no lo sé. ¿El registro int k & l se almacenará en un registro de CPU o no? Los usuarios de Linux (especialmente) deben compilar con GCC y optimización. Con Borland bcc32, la palabra clave de registro parece funcionar (en este ejemplo), ya que el operador & da códigos de error para registrar enteros declarados. ¡NOTA! ¡Este NO es el caso con un pequeño ejemplo con Borland en Windows! Para ver realmente qué optimiza o no el compilador, tiene que ser más que un pequeño ejemplo. ¡Los bucles vacíos no funcionarán! Sin embargo, si una dirección PUEDE leerse con el operador &, la variable no se almacena en un registro de CPU. Pero si un registro declarado variable no se puede leer (lo que provoca un código de error en la compilación), tengo que suponer que la palabra clave de registro realmente coloca la variable en un registro de CPU. Puede diferir en varias plataformas, no lo sé. (Si funciona, el número de "ticks" será mucho menor con la declaración de registro.

/* 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
fuente
Habrá una división con cero arriba, cambie {tmp + = ii / jj;} a {tmp + = jj / ii;} - realmente lo siento
John P Eriksson
También dejemos que k y yo comencemos con 1, no cero. Lo sentimos mucho.
John P Eriksson
3
Puede editar su respuesta en lugar de escribir correcciones en los comentarios.
Jan Doggen