Registrar palabra clave en C ++

89

Cual es la diferencia entre

int x=7;

y

register int x=7;

?

Estoy usando C ++.

dato datuashvili
fuente
8
@GMan: ANSI C no permite tomar la dirección de un objeto de registro; esta restricción no se aplica a C ++
Brian R. Bondy
1
@Brian: Hm, tienes razón. Ahora está solo en una nota (que probablemente se ignorará si se toma la dirección), pero no es obligatorio. Bueno saber. (Bueno, más o menos.: P)
GManNickG
8
Votar para reabrir registertiene una semántica diferente entre C y C ++.
CB Bailey
3
como consecuencia de esto, en C es posible prohibir la conversión de matriz a puntero haciendo un registro de matriz: register int a[1];con esa declaración, no puede indexar esa matriz. Si lo intenta, lo hace UB
Johannes Schaub - litb
2
De hecho, voté a favor de reabrir. Voté para cerrar antes de saber que había una diferencia.
GManNickG

Respuestas:

24

En C ++ tal como existía en 2010, cualquier programa válido que utilice las palabras clave "auto" o "registro" será semánticamente idéntico a uno con esas palabras clave eliminadas (a menos que aparezcan en macros en cadena u otros contextos similares). En ese sentido, las palabras clave son inútiles para compilar programas correctamente. Por otro lado, las palabras clave pueden ser útiles en ciertos contextos de macros para garantizar que el uso inadecuado de una macro provoque un error en tiempo de compilación en lugar de producir código falso.

En C ++ 11 y versiones posteriores del lenguaje, la autopalabra clave se rediseñó para actuar como un pseudo-tipo para los objetos que se inicializan, que un compilador reemplazará automáticamente con el tipo de la expresión de inicialización. Por lo tanto, en C ++ 03, la declaración: auto int i=(unsigned char)5;era equivalente a int i=5;cuando se usaba dentro de un contexto de bloque y auto i=(unsigned char)5;era una violación de restricción. En C ++ 11, se auto int i=(unsigned char)5;convirtió en una violación de restricción mientras se auto i=(unsigned char)5;convirtió en equivalente a auto unsigned char i=5;.

Super gato
fuente
22
Puede resultar útil un ejemplo del último bit.
Dennis Zickefoose
14
Esta respuesta ya no es correcta, desde 2011, la palabra clave autono se puede simplemente omitir ... Quizás podría actualizar su respuesta.
Walter
2
@Walter: ¿Puedes citar qué ha cambiado? No he seguido todos los cambios de idioma.
supercat
2
@supercat, sí, por el momento, pero registerestá desaprobado y habrá una propuesta para eliminarlo para C ++ 17.
Jonathan Wakely
3
De acuerdo con en.cppreference.com/w/cpp/language/auto , la publicación C ++ 11 autoahora se usa para la deducción automática de tipos Pero antes, se usaba para especificar que deseaba que su variable se almacenara "automáticamente" ( por lo tanto, en la pila , supongo) en lugar de la palabra clave register(que significa "registro del procesador"):
Guillaume
97

register es una pista para el compilador, aconsejándole que almacene esa variable en un registro del procesador en lugar de la memoria (por ejemplo, en lugar de la pila).

El compilador puede seguir o no esa sugerencia.

Según Herb Sutter en "Palabras clave que no son (o comentarios con otro nombre)" :

Un especificador de registro tiene la misma semántica que un especificador automático ...

Tom
fuente
2
Sin embargo, desde C ++ 17, está obsoleto, sin uso y reservado.
ZachB
@ZachB, esto es incorrecto; El registro está reservado en C ++ 17 pero todavía funciona y funciona casi de manera idéntica al registro de C.
Lewis Kelsey
@LewisKelsey No se utiliza y está reservado en la especificación C ++ 17; no es uno de los storage-class-specifierde la gramática y no tiene semántica definida. Un compilador conforme puede arrojar un error como lo hace Clang. No obstante, algunas implementaciones aún lo permiten y lo ignoran (MSVC, ICC) o lo usan como una sugerencia de optimización (GCC). Consulte open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . Sin embargo, me equivoqué en un punto: estaba obsoleto en C ++ 11.
ZachB
26

Con los compiladores de hoy, probablemente nada. Originalmente, era una sugerencia para colocar una variable en un registro para un acceso más rápido, pero la mayoría de los compiladores ignoran esa sugerencia y deciden por sí mismos.

KeithB
fuente
9

Casi con certeza nada.

registeres una pista para el compilador de que planea usar xmucho y que cree que debería colocarse en un registro.

Sin embargo, los compiladores ahora son mucho mejores para determinar qué valores deben colocarse en los registros que el programador promedio (o incluso experto), por lo que los compiladores simplemente ignoran la palabra clave y hacen lo que quieren.

James Curran
fuente
7

La registerpalabra clave fue útil para:

  • Montaje en línea.
  • Programación experta en C / C ++.
  • Declaración de variables almacenables en caché.

Un ejemplo de un sistema productivo, donde registerse requería la palabra clave:

typedef unsigned long long Out;
volatile Out out,tmp;
Out register rax asm("rax");
asm volatile("rdtsc":"=A"(rax));
out=out*tmp+rax;

Ha quedado obsoleto desde C ++ 11 y no se utiliza y está reservado en C ++ 17 .

ncomputadoras
fuente
2
Y agregaría que la palabra clave 'registrar' solo sería útil en un microcontrolador que ejecute un solo programa C ++ sin subprocesos y sin multitarea. El programa C ++ tendría que ser propietario de toda la CPU para asegurarse de que la variable 'registro' no se mueva de los registros especiales de la CPU.
Santiago Villafuerte
@SantiagoVillafuerte ¿quieres agregarlo editando la respuesta?
ncomputers
No estoy tan seguro de mi respuesta ... aunque suena plausible. Prefiero dejarlo como comentario para que otros lo aprueben o desaprueben.
Santiago Villafuerte
1
@SantiagoVillafuerte Esto no es realmente cierto, en los sistemas multitarea cuando el cambio de contexto del sistema operativo, no la aplicación, es responsable de guardar / restaurar los registros. Dado que no está cambiando de contexto después de cada instrucción de la CPU, poner cosas en registros es absolutamente significativo. Las otras respuestas aquí (que a los compiladores simplemente no les importa su opinión cuando se trata de la asignación de registros) son más precisas.
Cubic
El ejemplo que ha mostrado en realidad utiliza la extensión de Variables de registro explícitas de GCC , que es diferente del registerespecificador de clase de almacenamiento y aún es compatible con GCC.
ZachB
1

Considere un caso en el que el optimizador del compilador tiene dos variables y se ve obligado a derramar una en la pila. Ocurrió que ambas variables tienen el mismo peso para el compilador. Dado que no hay diferencia, el compilador derramará arbitrariamente una de las variables. Por otro lado, la registerpalabra clave le da al compilador una pista a qué variable se accederá con más frecuencia. Es similar a la instrucción de captación previa x86, pero para el optimizador del compilador.

Obviamente, las registersugerencias son similares a las sugerencias de probabilidad de ramificación proporcionadas por el usuario y se pueden inferir de estas sugerencias de probabilidad. Si el compilador sabe que alguna rama se toma con frecuencia, mantendrá las variables relacionadas con la rama en los registros. Por lo tanto, sugiero que se preocupe más por las sugerencias de rama y se olvide register. Idealmente, su generador de perfiles debería comunicarse de alguna manera con el compilador y evitar que usted piense siquiera en esos matices.

SmugLispWeenie
fuente
1

A partir de gcc 9.3, compilación usando -std=c++2a, register produce una advertencia del compilador, pero todavía tiene el efecto deseado y se comporta de forma idéntica a C de registeral compilar sin -O1 - parámetros de optimización Ofast en el respeto de esta respuesta. Sin embargo, el uso de clang ++ - 7 provoca un error del compilador. Así que sí, las registeroptimizaciones solo marcan la diferencia en la compilación estándar sin marcas de optimización -O, pero son optimizaciones básicas que el compilador descubriría incluso con -O1.

La única diferencia es que en C ++, se le permite tomar la dirección de la variable de registro, lo que significa que la optimización solo ocurre si no toma la dirección de la variable o sus alias (para crear un puntero) o toma una referencia de él en el código (solo en - O0, porque una referencia también tiene una dirección, porque es un puntero constante en la pila , que, como un puntero, puede optimizarse fuera de la pila si se compila con -Ofast, excepto que nunca aparecerán en la pila usando -Ofast, porque a diferencia de un puntero, no se pueden hacer volatiley sus direcciones no se pueden tomar), de lo contrario se comportará como si no lo hubiera usado register, y el valor se almacenará en la pila.

En -O0, otra diferencia es que const registeren gcc C y gcc C ++ no se comportan igual. En gcc C, se const registercomporta como register, porque block-scope consts no está optimizado en gcc. En clang C, registerno hace nada y solo se constaplican optimizaciones de alcance de bloque. En gcc C, se registeraplican optimizaciones pero consten el alcance del bloque no tiene optimización. En gcc C ++, se combinan las optimizaciones de alcance de bloque registery de constbloque.

#include <stdio.h> //yes it's C code on C++
int main(void) {
  const register int i = 3;
  printf("%d", i);
  return 0;
}

int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3
  mov eax, DWORD PTR [rbp-4]
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

register int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  push rbx
  sub rsp, 8
  mov ebx, 3
  mov esi, ebx
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  mov rbx, QWORD PTR [rbp-8] //callee restoration
  leave
  ret

const int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3 //still saves to stack
  mov esi, 3 //immediate substitution
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

const register int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  mov esi, 3 //loads straight into esi saving rbx push/pop and extra indirection (because C++ block-scope const is always substituted immediately into the instruction)
  mov edi, OFFSET FLAT:.LC0 // can't optimise away because printf only takes const char*
  mov eax, 0 //zeroed: https://stackoverflow.com/a/6212755/7194773
  call printf
  mov eax, 0 //default return value of main is 0
  pop rbp //nothing else pushed to stack -- more efficient than leave (rsp == rbp already)
  ret

registerle dice al compilador que 1) almacene una variable local en un registro guardado de la persona que llama, en este caso rbx, y 2) optimice las escrituras de la pila si nunca se toma la dirección de la variable . constle dice al compilador que sustituya el valor inmediatamente (en lugar de asignarle un registro o cargarlo desde la memoria) y escribir la variable local en la pila como comportamiento predeterminado. const registeres la combinación de estas optimizaciones envalentonadas. Esto es lo más delgado posible.

Además, en gcc C y C ++, registerpor sí solo parece crear un espacio aleatorio de 16 bytes en la pila para el primer local en la pila, lo que no sucede con const register.

Sin embargo, compilando usando -Ofast; registertiene un efecto de optimización 0 porque si se puede poner en un registro o inmediatamente, siempre lo será y si no, no lo será; consttodavía optimiza la carga en C y C ++ pero solo en el alcance del archivo ; volatiletodavía obliga a que los valores se almacenen y carguen desde la pila.

.LC0:
  .string "%d"
main:
  //optimises out push and change of rbp
  sub rsp, 8 //https://stackoverflow.com/a/40344912/7194773
  mov esi, 3
  mov edi, OFFSET FLAT:.LC0
  xor eax, eax //xor 2 bytes vs 5 for mov eax, 0
  call printf
  xor eax, eax
  add rsp, 8
  ret
Lewis Kelsey
fuente