Palabra clave "registrarse" en C?


273

¿Qué hace la registerpalabra 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?


42
¿Qué hace la palabra clave de registro en C? se ignora :)
bestsss

19
@bestsss No completamente ignorado. Intenta obtener una dirección de registervariable.
qrdl

44
Ese código que estás leyendo es antiguo youtube.com/watch?v=ibF36Yyeehw#t=1827
Coronel Panic el

Respuestas:


341

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.


18
Bueno, experimenté con el registro para ajustar mis envíos de ACM, y a veces realmente ayudó. Pero realmente tienes que ser cuidadoso, porque las malas elecciones degradan el rendimiento.
ypnos

81
Una buena razón para no usar 'registrarse': no ​​puede tomar la dirección de una variable declarada 'registrarse'
Adam Rosenfield

23
Tenga en cuenta que algunos / muchos compiladores ignorarán por completo la palabra clave de registro (que es perfectamente legal).
Euro Micelli

55
ypnos: En realidad, la velocidad de una solución para los problemas de ACM ICPC depende mucho más de la elección del algoritmo que de tales micro optimizaciones. El límite de tiempo de 5 segundos suele ser suficiente para una solución correcta, especialmente cuando se usa C en lugar de Java.
Joey el

66
@Euro: Probablemente lo sepas, pero para ser explícito, se requiere el compilador para evitar que registerse tome la dirección de una variable; Este es el único efecto obligatorio de la registerpalabra 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.
Dale Hagglund

69

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 registerno 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.


94
Hay una razón en realidad. El mero hecho de que no pueda tomar una dirección de la variable ofrece algunas oportunidades de optimización: el compilador puede demostrar que la variable no tendrá alias.
Alexandre C.

8
Los compiladores son notoriamente terribles al demostrar que el alias no ocurre en casos no triviales, por lo que registeres útil para esto incluso si el compilador no lo coloca en un registro.
Miles Rout

2
@AlexandreC, Miles, los compiladores están perfectamente bien para verificar si se toma una variable en cualquier lugar. Por lo tanto, independientemente de otras dificultades para detectar el alias, el replanteamiento no le ofrece nada. Cuando K + R creó C por primera vez, fue útil saber de antemano que & no se usaría, ya que ese compilador realmente tomó la decisión de asignación de registro al ver la declaración, antes de mirar el siguiente código. Es por eso que la prohibición está vigente. La palabra clave 'registrarse' es esencialmente obsoleta ahora.
greggo

25
Por esta lógica consttambién será inútil ya que no te gana nada, solo pierdes la capacidad de cambiar una variable. registerpodría usarse para asegurarse de que nadie tome la dirección de una variable en el futuro sin pensar. Sin registerembargo, nunca he tenido razones para usar .
Tor Klingberg

34

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.


8
Vale la pena añadir a las personas que usan C ++, C ++ le permite tomar la dirección de una variable de registro
Will

55
@Will: ... pero el compilador probablemente terminará ignorando la palabra clave como resultado. Mira mi respuesta.
bwDraco

Sí, parece que 'registrarse' es un placebo en C ++, solo está ahí para permitir que el código C se compile como C ++. Y no tendría mucho sentido prohibir & var mientras se permite pasarlo por referencia, o const-reference, y sin pasar por referencia has roto seriamente C ++.
greggo

22

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 registerespecificador 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 registerpalabra clave para permitir tomar la dirección.


17

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.


13

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.


"le dice al compilador .." no, no lo hace. Todas las variables automáticas tienen esa propiedad, a menos que tome su dirección y la use de maneras que excedan ciertos usos analizables. Entonces, el compilador lo sabe por el código, independientemente de si usa la palabra clave de registro. Sucede que la palabra clave 'registrarse' hace que sea ilegal escribir una construcción de este tipo, pero si no usa la palabra clave y no toma la dirección de esa manera, el compilador aún sabe que es segura. Dicha información es crucial para la optimización.
greggo

1
@greggo: Lástima que registerprohí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).
supercat

@supercat creo que aún sería una conversación muy difícil de mantener con el compilador. Si eso es lo que quiere decirle al compilador, puede hacerlo copiando la primera variable a una segunda que no tenga '&', y luego nunca vuelva a usar la primera.
greggo

1
@greggo: Decir que si se bartrata de una registervariable, un compilador puede reemplazarlo foo(&bar);con libertad int temp=bar; foo(&temp); bar=temp;, pero tomar la dirección de barestarí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.
supercat

1
@greggo: Permitir una registercalificació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?
supercat

13

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 registersugiere 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 registerno puede usarse en una declaración externa.

Hay algunas otras reglas (bastante oscuras) que son específicas de los registerobjetos calificados:

  • La definición de un objeto de matriz registertiene un comportamiento indefinido.
    Corrección: es legal definir un objeto de matriz register, pero no puede hacer nada útil con dicho objeto (indexar en una matriz requiere tomar la dirección de su elemento inicial).
  • los _Alignas especificador (nuevo en C11) no puede aplicarse a dicho objeto.
  • Si el nombre del parámetro pasado a la va_startmacro está registercalificado, 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 registerera 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 registera 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 registeres 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.


Entonces, ¿el comportamiento de este programa es realmente indefinido según el estándar C? ¿Está bien definido en C ++? Creo que está bien definido en C ++.
Destructor

@ Destructor: ¿Por qué sería indefinido? No hay registerningún objeto de matriz calificado, si eso es lo que estás pensando.
Keith Thompson

Oh, lo siento, olvidé escribir la palabra clave de registro en la declaración de matriz en main (). ¿Está bien definido en C ++?
Destructor

Me equivoqué al definir registerobjetos 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 registera la definición de sen 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 registersería inútil).
Keith Thompson

@KeithThompson: la registerpalabra 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.
supercat

9

¡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.


5

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.


13
Algunos de los equipos más antiguos tenían más registros que las máquinas modernas de Intel. Los recuentos de registros no tienen nada que ver con la edad y todo que ver con la arquitectura de la CPU.
SOLO MI OPINIÓN correcta

2
@JUSTMYcorrectOPINION De hecho, X86 tiene básicamente seis en total, dejando a lo sumo 1 o 2 para dedicarse a 'registrarse'. De hecho, dado que se ha escrito o portado tanto código a una máquina con registro deficiente, sospecho que esto contribuyó en gran medida a que la palabra clave 'registrarse' se convirtiera en un placebo, no tiene sentido insinuar registros cuando no hay ninguno. Aquí estamos más de 4 años más tarde y afortunadamente x86_64 lo ha elevado a 14, y ARM es una gran cosa ahora también.
greggo

4

Solo una pequeña demostración (sin ningún propósito del mundo real) para comparar: al eliminar las registerpalabras 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;
}

2
Con gcc 4.8.4 y -O3, no obtengo ninguna diferencia. Sin iteraciones -O3 y 40000, obtengo quizás 50 ms menos en un tiempo total de 1.5s, pero no lo ejecuté suficientes veces para saber si eso fue estadísticamente significativo.
zstewart

No hay diferencia con CLANG 5.0, la plataforma es AMD64. (He comprobado la salida de ASM.)
ern0

4

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


2

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.


2

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.

c_register_side_effect_performance_boost


1

En los compiladores de C compatibles, intenta optimizar el código para que el valor de la variable se mantenga en un registro de procesador real.


1

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.


1

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.


0

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.


0

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 ebxa 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. registerproduce 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 registerproducen 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). registerno tiene este requisito y no se puede señalar. consty registerson básicamente lo contrario devolatile y usandovolatileanulará las optimizaciones constantes en el alcance del archivo y del bloque y las registeroptimizaciones en el alcance del bloque. const registery registerproducirá salidas idénticas porque const no hace nada en C en el alcance de bloque, por lo que solo se registeraplican las optimizaciones.

En el sonido metálico, registerse ignora pero constaún se producen optimizaciones.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.