Estoy confundido acerca del uso de la palabra clave register
en C. Generalmente se dice que su uso no » Se necesita como en esta pregunta sobre 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 la palabra clave register
es realmente útil?
Comentarios
- 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í.
- @UwePlonus Pensé que Lo mismo con la
const
palabra clave, pero esta pregunta demostró que estaba equivocado. Así que ‘ Esperaré y veré qué obtengo. - Creo que la palabra clave
const
es algo diferente a la de registro. - Es ‘ es útil si accidentalmente retrocede 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.
- @ UwePlonus Solo quise decir que puede haber escenarios desconocidos para mí en los que una palabra clave podría ser útil.
Respuesta
No es redundante en términos de lenguaje, es solo que al usarlo, le estás diciendo al compilador que «preferirías» tener una variable almacenada en el registro. Sin embargo, no hay absolutamente ninguna garantía de que esto realmente suceden durante el tiempo de ejecución.
Comentarios
- Más que eso, ‘ es casi siempre el caso de que el compilador sabe más y usted ‘ está perdiendo el aliento
- @jozefg: peor aún. Corre el riesgo de que el compilador respete su solicitud / sugerencia y produzca peor código como resultado.
Respuesta
Como ya se mencionó, los optimizadores del compilador esencialmente ren der la palabra clave register
obsoleta para otros propósitos que no sean prevenir el alias. Sin embargo, hay bases de código completas que se compilan con la optimización desactivada (-O0
en gcc-speak ). Para dicho código, la palabra clave register
puede tener un gran efecto. Específicamente, las variables que de otra manera obtendrían un espacio en la pila (es decir, todos los parámetros de función y variables automáticas) pueden colocarse directamente en un registro si se declaran con register
palabra clave.
Aquí «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 sea un registro de caché que representa los metadatos almacenados en la base de datos que, debido a limitaciones de memoria, solo almacena en caché un subconjunto de cada registro de metadatos tal como se almacena 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 a partir de los miembros de una estructura a las otras, las direcciones de la estructura, para cada asignación, cargarse en un registro para realizar el acceso de los miembros de la estructura que se están copiando. Si los punteros de la estructura fueran declarados con la palabra clave register
, las direcciones de las estructuras permanecerían en los registros, eliminando efectivamente las instrucciones de carga de dirección en registro para cada asignación.
Una vez más, recuerde que la descripción anterior se aplica al código no optimizado .
Respuesta
Básicamente, le dices al compilador que no vas a tomar la dirección de la variable y el compilador puede entonces aparentemente hacer optimizaciones adicionales. Hasta donde yo 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; }
Comentarios
- ¿Desreferenciar o tomar la dirección de?
- @detly: por supuesto que tiene razón
Respuesta
En los días de las computadoras de 16 bits, a menudo se necesitaban varios registros para ejecutar multiplicaciones y divisiones de 32 bits. las unidades de punto flotante se incorporaron a los chips y luego las arquitecturas de 64 bits «asumieron el control», expandiendo tanto el ancho de los registros como el número de ellos. Esto eventualmente conduce a una reestructuración completa de la CPU. Ver Registrar archivos en Wikipedia.
En resumen, te tomaría un poco de tiempo figu averigüe lo que está pasando realmente si está en un chip X86 o ARM de 64 bits.Si está en una CPU integrada de 16 bits, esto podría proporcionarle algo. Sin embargo, la mayoría de los chips integrados pequeños no ejecutan nada crítico en el tiempo: su horno de microondas puede estar muestreando su panel táctil 10,000 veces por segundo, nada que fuerce a CPU de 4Mhz.
Comentarios
- 4 MIPS / 10,000 encuestas / seg = 400 instrucciones / encuesta. Ese ‘ no tiene tanto margen como ‘ le gustaría tener. También tenga en cuenta que bastantes procesadores de 4 MHz se microcodificaron internamente, lo que significa que no estaban cerca de 1 MIP / MHz.
- @ JohnR.Strohm: puede haber situaciones en las que uno podría justificar averiguar exactamente cuántas instrucciones cicla ‘ s, pero a menudo la salida más barata ahora es simplemente obtener un chip más rápido y sacar el producto por la puerta. En el ejemplo dado, por supuesto, uno no ‘ t tiene que continuar muestreando a 10,000 si uno tiene un comando – podría no reanudar el muestreo por un cuarto de segundo sin ningún daño hecho. Cada vez es más difícil averiguar dónde va a importar la optimización dirigida por el programador.
- No siempre es posible » simplemente obtener un chip más rápido y obtener el producto fuera de la puerta «. Considere el procesamiento de imágenes en tiempo real. 640×480 píxeles / fotograma x 60 fotogramas / segundo x N instrucciones por píxel se agregan rápidamente. (La lección del procesamiento de imágenes en tiempo real es que sudas sangre sobre los 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 cuadro, a diferencia de cientos de veces por línea o parche o decenas o cientos de miles de veces por cuadro.)
- @ JohnR.Strohm – tomando el ejemplo de procesamiento de imágenes en tiempo real, supongo que el entorno mínimo es de 32 bits. Al arriesgarme (porque no ‘ sé lo práctico que es hacerlo), muchos aceleradores de gráficos integrados en chips también pueden utilizarse 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 ‘ register ‘ para la optimización es una pequeña parte del problema.
Respuesta
Para establecer si la palabra clave de registro tiene algún significado, los códigos de ejemplo pequeños no lo harán. Aquí hay un código c lo que me sugiere que la palabra clave register 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 register parece funcionar (en este ejemplo), ya que el operador & da códigos de error para los enteros declarados por el registro. ¡NOTA! ¡Este NO es el caso de 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 funcionan. Sin embargo, si una dirección PUEDE leerse con el operador &, la variable no se almacena en un registro de la CPU. Pero si una variable declarada de registro no se puede leer (lo que causa un código de error en la compilación), debo 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=ⅆ ep=&e; eep=ⅇ fp=&f; ffp=&ff; gp=&g; ggp=≫ hp=&h; hhp=&hh; ip=&i; iip=ⅈ 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; }
Comentarios
- Habrá una división con cero arriba, por favor cambie {tmp + = ii / jj;} a {tmp + = jj / ii;} – lo siento mucho por esto
- También deje k e i comience con 1 – no cero. Lo siento mucho.
- Puede editar su respuesta en lugar de escribir correcciones en los comentarios.