¿Qué hace la register
palabra clave en lenguaje C? He leído que se usa para optimizar pero no está claramente definido en ningún estándar. ¿Sigue siendo relevante y, de ser así, cuándo lo usarías?
register
variable.
¿Qué hace la register
palabra clave en lenguaje C? He leído que se usa para optimizar pero no está claramente definido en ningún estándar. ¿Sigue siendo relevante y, de ser así, cuándo lo usarías?
register
variable.
Respuestas:
Es una pista para el compilador que la variable se utilizará en gran medida y que, si es posible, se recomienda mantenerla en un registro del procesador.
La mayoría de los compiladores modernos lo hacen automáticamente, y son mejores para elegirlos que nosotros los humanos.
register
se tome la dirección de una variable; Este es el único efecto obligatorio de la register
palabra clave. Incluso esto es suficiente para mejorar las optimizaciones, porque resulta trivial decir que la variable solo se puede modificar dentro de esta función.
Me sorprende que nadie mencione que no puede tomar una dirección de variable de registro, incluso si el compilador decide mantener la variable en la memoria en lugar de en el registro.
Por lo tanto, si register
no gana nada (de todos modos, el compilador decidirá por sí mismo dónde colocar la variable) y perderá el &
operador, sin razón para usarlo.
register
es útil para esto incluso si el compilador no lo coloca en un registro.
const
también será inútil ya que no te gana nada, solo pierdes la capacidad de cambiar una variable. register
podría usarse para asegurarse de que nadie tome la dirección de una variable en el futuro sin pensar. Sin register
embargo, nunca he tenido razones para usar .
Le dice al compilador que intente usar un registro de CPU, en lugar de RAM, para almacenar la variable. Los registros están en la CPU y tienen un acceso mucho más rápido que la RAM. Pero es solo una sugerencia para el compilador, y puede que no se cumpla.
Sé que esta pregunta es sobre C, pero la misma pregunta para C ++ se cerró como un duplicado exacto de esta pregunta. Por lo tanto, esta respuesta puede no aplicarse para C.
El último borrador del estándar C ++ 11, N3485 , dice esto en 7.1.1 / 3:
Un
register
especificador es una pista para la implementación de que la variable así declarada será muy utilizada. [ nota: la sugerencia se puede ignorar y en la mayoría de las implementaciones se ignorará si se toma la dirección de la variable. Este uso está en desuso ... —final nota ]
En C ++ (pero no en C), el estándar no establece que no se puede tomar la dirección de una variable declarada register
; sin embargo, debido a que una variable almacenada en un registro de CPU a lo largo de su vida útil no tiene una ubicación de memoria asociada, intentar tomar su dirección sería inválido, y el compilador ignorará la register
palabra clave para permitir tomar la dirección.
No ha sido relevante durante al menos 15 años, ya que los optimizadores toman mejores decisiones sobre esto que usted. Incluso cuando era relevante, tenía mucho más sentido en una arquitectura de CPU con muchos registros, como SPARC o M68000, que en Intel con su escasez de registros, la mayoría de los cuales están reservados por el compilador para sus propios fines.
En realidad, el registro le dice al compilador que la variable no se alias con nada más en el programa (ni siquiera el de char).
Los compiladores modernos pueden aprovecharlo en una variedad de situaciones y pueden ayudar al compilador bastante en código complejo; en un código simple, los compiladores pueden resolver esto por sí mismos.
De lo contrario, no sirve para nada y no se utiliza para la asignación de registros. Por lo general, no se produce una degradación del rendimiento para especificarlo, siempre que su compilador sea lo suficientemente moderno.
register
prohíba tomar la dirección, ya que de lo contrario podría ser útil informar a los compiladores de los casos en que un compilador podría aplicar optimizaciones de registro a pesar de que la dirección de una variable se pasa a una función externa (la variable tendría que se enjuague a la memoria para esa llamada en particular , pero una vez que la función regrese, el compilador podría volver a tratarla como una variable cuya dirección nunca se haya tomado).
bar
trata de una register
variable, un compilador puede reemplazarlo foo(&bar);
con libertad int temp=bar; foo(&temp); bar=temp;
, pero tomar la dirección de bar
estaría prohibido en la mayoría de los otros contextos no parecería una regla demasiado complicada. Si la variable pudiera mantenerse en un registro, la sustitución reduciría el código. Si la variable necesitara mantenerse en la RAM de todos modos, la sustitución agrandaría el código. Dejar la pregunta de si hacer la sustitución hasta el compilador conduciría a un mejor código en ambos casos.
register
calificación en variables globales, ya sea que el compilador permita o no que se tome la dirección, permitiría algunas optimizaciones agradables en los casos en que una función en línea que usa una variable global se llama repetidamente en un bucle. No se me ocurre otra forma de dejar que esa variable se mantenga en un registro entre iteraciones de bucle, ¿puedes?
He leído que se usa para optimizar pero no está claramente definido en ningún estándar.
De hecho, está claramente definido por el estándar C. Citando el borrador de N1570 sección 6.7.1 párrafo 6 (otras versiones tienen la misma redacción):
Una declaración de un identificador para un objeto con especificador de clase de almacenamiento
register
sugiere que el acceso al objeto sea lo más rápido posible. La medida en que tales sugerencias son efectivas está definida por la implementación.
El &
operador unario no puede aplicarse a un objeto definido con register
, y register
no puede usarse en una declaración externa.
Hay algunas otras reglas (bastante oscuras) que son específicas de los register
objetos calificados:
register
tiene un comportamiento indefinido. register
, pero no puede hacer nada útil con dicho objeto (indexar en una matriz requiere tomar la dirección de su elemento inicial)._Alignas
especificador (nuevo en C11) no puede aplicarse a dicho objeto.va_start
macro está register
calificado, el comportamiento es indefinido.Puede haber algunos otros; descargue un borrador del estándar y busque "registrarse" si está interesado.
Como su nombre lo indica, el significado original de register
era requerir que un objeto se almacenara en un registro de CPU. Pero con las mejoras en la optimización de compiladores, esto se ha vuelto menos útil. Las versiones modernas del estándar C no se refieren a los registros de la CPU, porque ya no (necesitan) asumir que existe tal cosa (hay arquitecturas que no usan registros). La sabiduría común es que la aplicación register
a una declaración de objeto es más probable que empeore el código generado, ya que interfiere con la asignación del registro del compilador. Todavía puede haber algunos casos en los que sea útil (por ejemplo, si realmente sabe con qué frecuencia se accederá a una variable, y su conocimiento es mejor de lo que puede descubrir un compilador de optimización moderno).
El principal efecto tangible de register
es que evita cualquier intento de tomar la dirección de un objeto. Esto no es particularmente útil como una sugerencia de optimización, ya que puede aplicarse solo a variables locales, y un compilador de optimización puede ver por sí mismo que no se toma la dirección de dicho objeto.
register
ningún objeto de matriz calificado, si eso es lo que estás pensando.
register
objetos de matriz; vea el primer punto de viñeta actualizado en mi respuesta. Es legal definir dicho objeto, pero no puede hacer nada con él. Si agrega register
a la definición de s
en su ejemplo , el programa es ilegal (una violación de restricción) en C. C ++ no aplica las mismas restricciones register
, por lo que el programa sería válido C ++ (pero el uso register
sería inútil).
register
palabra clave podría tener un propósito útil si fuera legal tomar la dirección de dicha variable, pero solo en los casos en que la semántica no se vería afectada al copiar la variable en un temporal cuando se toma su dirección y volver a cargarla desde el temporal en el siguiente punto de secuencia. Eso permitiría a los compiladores suponer que la variable se puede mantener de forma segura en un registro en todos los accesos de puntero siempre que se vacíe en cualquier lugar donde se tome su dirección.
¡Tiempo de cuentos!
C, como lenguaje, es una abstracción de una computadora. Le permite hacer cosas, en términos de lo que hace una computadora, que es manipular la memoria, hacer matemáticas, imprimir cosas, etc.
Pero C es solo una abstracción. Y en última instancia, lo que se extrae de ti es lenguaje ensamblador. El ensamblado es el lenguaje que lee una CPU, y si lo usa, hace las cosas en términos de la CPU. ¿Qué hace una CPU? Básicamente, lee de la memoria, hace matemáticas y escribe en la memoria. La CPU no solo hace cálculos matemáticos sobre números en la memoria. Primero, debe mover un número de memoria a memoria dentro de la CPU llamado a registro. Una vez que haya terminado de hacer lo que sea necesario para este número, puede moverlo nuevamente a la memoria normal del sistema. ¿Por qué usar la memoria del sistema? Los registros son limitados en número. Solo obtienes alrededor de cien bytes en procesadores modernos, y los procesadores populares más antiguos estaban aún más fantásticamente limitados (El 6502 tenía 3 registros de 8 bits para tu uso gratuito). Entonces, su operación matemática promedio se ve así:
load first number from memory
load second number from memory
add the two
store answer into memory
Mucho de eso es ... no matemáticas. Esas operaciones de carga y almacenamiento pueden tomar hasta la mitad de su tiempo de procesamiento. C, al ser una abstracción de las computadoras, liberó al programador de la preocupación de usar y hacer malabarismos con los registros, y dado que el número y el tipo varían entre las computadoras, C asigna la responsabilidad de la asignación de registros únicamente al compilador. Con una excepcion.
Cuando declaras una variable register
, le está diciendo al compilador "Yo, tengo la intención de que esta variable se use mucho y / o sea de corta duración. Si yo fuera usted, trataría de mantenerla en un registro". Cuando el estándar C dice que los compiladores no tienen que hacer nada, es porque el estándar C no sabe para qué computadora está compilando, y podría ser como el 6502 anterior, donde los 3 registros son necesarios solo para operar , y no hay un registro adicional para guardar su número. Sin embargo, cuando dice que no puede tomar la dirección, es porque los registros no tienen direcciones. Son las manos del procesador. Dado que el compilador no tiene que darle una dirección, y dado que nunca puede tener una dirección, ahora hay varias optimizaciones abiertas para el compilador. Podría, por ejemplo, mantener el número en un registro siempre. No lo hace No tiene que preocuparse por dónde está almacenado en la memoria de la computadora (más allá de la necesidad de recuperarlo nuevamente). Incluso podría incluirlo en otra variable, dárselo a otro procesador, darle una ubicación cambiante, etc.
tl; dr: Variables de corta duración que hacen muchas matemáticas. No declares demasiados a la vez.
Estás jugando con el sofisticado algoritmo de coloreado de gráficos del compilador. Esto se usa para la asignación de registros. Bueno, mayormente. Actúa como una pista para el compilador, eso es cierto. Pero no se ignora en su totalidad ya que no se le permite tomar la dirección de una variable de registro (recuerde que el compilador, ahora a su merced, intentará actuar de manera diferente). Lo que de alguna manera te está diciendo que no lo uses.
La palabra clave se utilizó mucho, mucho tiempo atrás. Cuando solo había unos pocos registros que pudieran contarlos todos con el dedo índice.
Pero, como dije, en desuso no significa que no pueda usarlo.
Solo una pequeña demostración (sin ningún propósito del mundo real) para comparar: al eliminar las register
palabras clave antes de cada variable, este fragmento de código tarda 3,41 segundos en mi i7 (GCC), con register
el mismo código completo en 0,7 segundos.
#include <stdio.h>
int main(int argc, char** argv) {
register int numIterations = 20000;
register int i=0;
unsigned long val=0;
for (i; i<numIterations+1; i++)
{
register int j=0;
for (j;j<i;j++)
{
val=j+i;
}
}
printf("%d", val);
return 0;
}
He probado la palabra clave de registro bajo QNX 6.5.0 usando el siguiente código:
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int main(int argc, char *argv[]) {
uint64_t cps, cycle1, cycle2, ncycles;
double sec;
register int a=0, b = 1, c = 3, i;
cycle1 = ClockCycles();
for(i = 0; i < 100000000; i++)
a = ((a + b + c) * c) / 2;
cycle2 = ClockCycles();
ncycles = cycle2 - cycle1;
printf("%lld cycles elapsed\n", ncycles);
cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
printf("This system has %lld cycles per second\n", cps);
sec = (double)ncycles/cps;
printf("The cycles in seconds is %f\n", sec);
return EXIT_SUCCESS;
}
Obtuve los siguientes resultados:
-> 807679611 ciclos transcurridos
-> Este sistema tiene 3300830000 ciclos por segundo
-> Los ciclos en segundos son ~ 0.244600
Y ahora sin registro int:
int a=0, b = 1, c = 3, i;
Tengo:
-> 1421694077 ciclos transcurridos
-> Este sistema tiene 3300830000 ciclos por segundo
-> Los ciclos en segundos son ~ 0.430700
Register notificaría al compilador que el codificador creía que esta variable se escribiría / leería lo suficiente como para justificar su almacenamiento en uno de los pocos registros disponibles para uso variable. Leer / escribir desde registros suele ser más rápido y puede requerir un conjunto de códigos operativos más pequeño.
Hoy en día, esto no es muy útil, ya que la mayoría de los optimizadores de compiladores son mejores que usted para determinar si un registro debe usarse para esa variable y durante cuánto tiempo.
Durante los años setenta, al comienzo del lenguaje C, se introdujo la palabra clave de registro para permitir al programador dar pistas al compilador, diciéndole que la variable se usaría con mucha frecuencia, y que debería ser conveniente mantenga su valor en uno de los registros internos del procesador.
Hoy en día, los optimizadores son mucho más eficientes que los programadores para determinar las variables que tienen más probabilidades de mantenerse en los registros, y el optimizador no siempre tiene en cuenta la sugerencia del programador.
Muchas personas recomiendan erróneamente no utilizar la palabra clave de registro.
¡A ver por qué!
La palabra clave de registro tiene un efecto secundario asociado: no puede hacer referencia (obtener la dirección de) una variable de tipo de registro.
Las personas que aconsejan a otros que no usen registros toman erróneamente esto como un argumento adicional.
Sin embargo, el simple hecho de saber que no puede tomar la dirección de una variable de registro permite al compilador (y su optimizador) saber que el valor de esta variable no puede modificarse indirectamente a través de un puntero.
Cuando en un cierto punto del flujo de instrucciones, una variable de registro tiene su valor asignado en el registro de un procesador, y el registro no se ha utilizado desde entonces para obtener el valor de otra variable, el compilador sabe que no necesita volver a cargar El valor de la variable en ese registro. Esto permite evitar el costoso acceso a memoria inútil.
Haga sus propias pruebas y obtendrá mejoras significativas en el rendimiento de sus bucles más internos.
El compilador de Visual C ++ de Microsoft ignora el register
palabra clave cuando la optimización de asignación de registro global (el indicador del compilador / Oe) está habilitada.
Consulte registrar palabra clave en MSDN.
La palabra clave Register le dice al compilador que almacene la variable particular en los registros de la CPU para que pueda accederse rápidamente. Desde el punto de vista del programador, la palabra clave de registro se usa para las variables que se usan mucho en un programa, de modo que el compilador pueda acelerar el código. Aunque depende del compilador si se debe mantener la variable en los registros de la CPU o en la memoria principal.
El registro indica al compilador que optimice este código almacenando esa variable particular en registros y luego en la memoria. es una solicitud al compilador, el compilador puede o no considerar esta solicitud. Puede usar esta función en caso de que algunas de sus variables se accedan con mucha frecuencia. Por ejemplo: un bucle.
Una cosa más es que si declara una variable como registro, entonces no puede obtener su dirección ya que no está almacenada en la memoria. obtiene su asignación en el registro de la CPU.
Salida de gcc 9.3 asm, sin usar indicadores de optimización (todo en esta respuesta se refiere a la compilación estándar sin indicadores de optimización):
#include <stdio.h>
int main(void) {
int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 3
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
#include <stdio.h>
int main(void) {
register int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
mov ebx, 3
add ebx, 1
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add rsp, 8
pop rbx
pop rbp
ret
Esto obliga ebx
a ser utilizado para el cálculo, lo que significa que debe ser empujado a la pila y restaurado al final de la función porque está guardado. register
produce más líneas de código y 1 escritura de memoria y 1 lectura de memoria (aunque de manera realista, esto podría haberse optimizado a 0 R / W si el cálculo se hubiera realizado esi
, que es lo que sucede usando C ++ const register
). Si no se usan, se register
producen 2 escrituras y 1 lectura (aunque se producirá un reenvío de almacenamiento a carga en la lectura). Esto se debe a que el valor debe estar presente y actualizado directamente en la pila para que el valor correcto pueda leerse por dirección (puntero). register
no tiene este requisito y no se puede señalar. const
y register
son básicamente lo contrario devolatile
y usandovolatile
anulará las optimizaciones constantes en el alcance del archivo y del bloque y las register
optimizaciones en el alcance del bloque. const register
y register
producirá salidas idénticas porque const no hace nada en C en el alcance de bloque, por lo que solo se register
aplican las optimizaciones.
En el sonido metálico, register
se ignora pero const
aún se producen optimizaciones.