C/C++ Palabras clave más raras: registro, volátil, externo, explícito [closed]

11 minutos de lectura

Avatar de usuario de John Humphreys
Juan Humphreys

¿Puede darme un resumen rápido de para qué se usan estas 4 palabras clave y por qué?

Entiendo los conceptos básicos que Google le diría sobre el registro y la volatilidad, pero me gustaría saber un poco más (solo una descripción general práctica). Externo y explícito me confunden un poco, ya que nunca encontré una razón para tener que usarlos yo mismo a pesar de hacer un código de sistemas integrados de nivel bastante bajo. Una vez más, puedo buscar en Google, pero prefiero un resumen rápido y práctico de un experto para que se me quede en la mente.

  • ¿Ha probado algún libro de texto estándar de C++, o incluso Wikipedia?

    – KerrekSB

    27 de julio de 2011 a las 23:39

  • extern ¿es raro?

    – BoltClock

    27 de julio de 2011 a las 23:40

  • explicit ciertamente no es una palabra clave “más rara”. Yo uso el explicit palabra clave mucho más que los otros tres juntos.

    – En silicio

    27 de julio de 2011 a las 23:43


  • Gracioso, nunca usé explicit y register en mi vida, pero uso extern mucho, y a veces incluso volatile.

    – Ciego

    27 de julio de 2011 a las 23:46

  • te quedaste fuera restrict.

    – Chris Lutz

    27 de julio de 2011 a las 23:52

Avatar de usuario de Nicol Bolas
Nicolás Bolas

externo

extern está sobrecargado para varios usos. Para variables globales, significa que está declarando la variable, no definiéndola. Esto es útil para poner variables globales en los encabezados. Si pones esto en un encabezado:

int someInteger;

Cada archivo .cpp que incluya ese encabezado intentará tener su propio someInteger. Eso causará un error del enlazador. Al declararlo con externtodo lo que estás diciendo es que habrá un someInteger en algún lugar del código:

extern int someInteger;

Ahora, en un archivo .cpp, puede definir int someIntegerde modo que habrá exactamente una copia de la misma.

También hay extern "C", que se usa para especificar que ciertas funciones usan reglas de enlace C en lugar de C++. Esto es útil para interactuar con bibliotecas y código compilado como C.

En C++0x, también habrá extern template declaraciones. Estos son lo opuesto a la instanciación de plantilla explícita. Cuando haces esto:

template class std::vector<int>;

Le está diciendo al compilador que cree una instancia de esta plantilla ahora mismo. Normalmente, la creación de instancias se retrasa hasta el primer uso de la plantilla. En C++ 0x, puede decir:

extern template class std::vector<int>;

Esto le dice al compilador no para instanciar esta plantilla en este archivo .cpp, alguna vez. De esa forma, puede controlar dónde se instancian las plantillas. El uso juicioso de esto puede mejorar sustancialmente los tiempos de compilación.

explícito

Esto se utiliza para evitar conversiones automáticas de tipos. si tienes una clase ClassName con el siguiente constructor:

ClassName(int someInteger);

Esto significa que si tienes una función que toma un ClassNameel usuario puede llamarlo con un inty la conversión se realizará automáticamente.

void SomeFunc(const ClassName &className);
SomeFunc(3);

Eso es legal, porque ClassName tiene un constructor de conversión que toma un número entero. Así funcionan las funciones que toman std::string también puede tomar un char*; std::string tiene un constructor que toma un char*.

Sin embargo, la mayoría de las veces no desea conversiones implícitas como esta. Por lo general, solo desea que las conversiones sean explícitas. Sí, a veces es útil como con std::string, pero necesita una forma de desactivarlo para las conversiones que no son apropiadas. Ingresar explicit:

explicit ClassName(int someInteger);

Esto evitará conversiones implícitas. Todavía puedes usar SomeFunc(ClassName(3)); pero SomeFunc(3) ya no funcionará.

Por cierto: si explicit es raro para ti, entonces no lo estás usando lo suficiente. Debe usarlo en todo momento, a menos que desee específicamente la conversión. Lo cual no es tan frecuente.

volátil

Esto evita ciertas optimizaciones útiles. Normalmente, si tiene una variable, C/C++ asumirá que sus contenidos solo cambiarán si los cambia explícitamente. Así que si declaras un int someInteger; como una variable global, los compiladores de C/C++ pueden almacenar en caché el valor localmente y no acceder constantemente al valor cada vez que lo usa.

A veces, quieres detener esto. En esos casos, se utiliza volatile; esto evita esas optimizaciones.

registro

Esto es solo una pista. Le dice al compilador que intente poner los datos de la variable en un registro. Es esencialmente innecesario; los compiladores son mejores que usted para decidir qué debe y qué no debe ser un registro.

  • +1 – gran respuesta en general. Re volatile, “solo cambie si los cambia explícitamente”: es decir, en el código/flujo circundante, y no a través de controladores de interrupción u otros subprocesos. Por separado, volatile también asegura que escribe a la variable crear escrituras a nivel de código de máquina en la memoria (y no solo un registro), eso no significa necesariamente que la memoria caché específica del núcleo de la CPU vaciará esa escritura hasta la memoria física, por lo que no es una alternativa a las barreras o bloqueos de memoria.

    – Tony Delroy

    28 de julio de 2011 a las 2:43

  • Un caso potencialmente legítimo para register: algunas CPU tienen registros más grandes que el contenido de la memoria asociada, por ejemplo, registros de punto flotante de 80 bits con redondeo a dobles de 64 bits cuando se escriben en la memoria. Tal vez el uso explícito de register podría ayudar a controlar cuándo se realiza esa pérdida de precisión, dando prioridad a un valor particular como un total a pesar de muchos otros valores utilizados para los cálculos entre las actualizaciones de ese total…?

    – Tony Delroy

    28 de julio de 2011 a las 2:49

  • register puede ser útil en los casos integrados, en los que la sincronización exacta entre dos eventos debe medirse en ciclos de CPU. Para compiladores integrados, el register La palabra clave generalmente no es solo una pista, sino un comando estricto.

    – vsz

    23 de enero de 2014 a las 7:26

register se usa como una pista para el compilador de que una variable debe almacenarse en un registro en lugar de en la pila. Los compiladores frecuentemente ignorarán esto y harán lo que quieran; las variables se asignarán a los registros si es posible de todos modos.

volatile indica que la memoria puede cambiar sin que el programa realmente haga nada. Esta es otra pista para el compilador de que debe evitar optimizar los accesos a esa ubicación. Por ejemplo, si tiene dos escrituras consecutivas en la misma ubicación sin lecturas intermedias, el compilador podría optimizar la primera. Sin embargo, si la ubicación en la que está escribiendo es un registro de hardware, necesitará que se realice cada escritura, exactamente como está escrita. Entonces volatile es como decir “solo confía en mí en esto”.

extern indica que se produce una definición fuera del archivo actual. Útil para variables globales, pero generalmente implícita en una declaración de todos modos. Como señala Blindy, también es útil para indicar que una función debe tener enlace C. Una función con enlace C se compilará utilizando su nombre real como su símbolo en el ejecutable de salida. Las funciones de C++ incluyen más información, como los tipos de sus argumentos en sus símbolos. Esta es la razón por la que la sobrecarga funciona en C++ pero no en C.

explicit se aplica a los constructores de C++. Significa que el constructor no debe llamarse implícitamente. Por ejemplo, digamos que tiene un Array clase con un constructor que acepta un entero capacity. No desea que los valores enteros se conviertan implícitamente en Array objetos solo porque hay un constructor entero.

register se ignora en su mayoría en estos días, pero se supone que debe insinuar al compilador que prefiere que la variable esté en un registro en lugar de en la pila. Los optimizadores hacen un trabajo tan bueno en estos días que ahora es irrelevante.

volatile le dice al compilador que no asuma que el valor solo se cambia desde el hilo actual, por lo que siempre leerá el valor de memoria real en lugar de almacenarlo en caché entre lecturas. Es útil para aplicaciones de subprocesos múltiples, pero de todos modos es mejor usar primitivas del sistema operativo (eventos, semáforos, etc.).

extern no define el valor, solo lo declara. Se usa principalmente para exportar e importar funciones desde DLL o bibliotecas compartidas (.a). Un efecto secundario también le permite desactivar la manipulación de nombres para C++ usando extern "C".

Y explicit le permite especificar que un constructor tiene que ser explícito, a diferencia de los constructores de conversión implícitos (donde puede escribir CMyClass val=10; si tiene un constructor implícito que toma un int).

  • volatile es necesario para compartir el acceso con el hardware, ninguna primitiva del sistema operativo lo ayudará allí.

    – littleadv

    27 de julio de 2011 a las 23:46

  • Bueno, estaba hablando de programas de espacio de usuario, donde el sistema operativo generalmente proporciona API para permitir un acceso “agradable” a los registros de hardware. El código del kernel es una historia diferente, por supuesto.

    – Ciego

    27 de julio de 2011 a las 23:48

El registro se usa para indicar al compilador que use un registro para almacenar este valor, los compiladores modernos optimizan para usar esto durante los bucles for y similares.

volatile son variables que pueden ser modificadas por un proceso externo o durante tiempos de ejecución de aplicaciones multiproceso.

extern le dice al enlazador que la variable está definida en un archivo diferente.

explicit indica al compilador que no permita conversiones implícitas de tipo.

Avatar de usuario de Thomas Russell
Tomas Russell

1: El registro se usa cuando desea forzar que un valor se mantenga en el registro en lugar de guardarlo en la RAM. Por ejemplo, podría hacer lo siguiente:

register int x;

Esto le permitiría al compilador saber que desea que este int se coloque en el registro de la CPU, lo que debería brindarle un acceso más rápido. Sin embargo, su compilador puede ignorar esta palabra clave en el proceso de optimización y la mayoría de las veces los buenos compiladores colocarán variables en los registros si es necesario. Es principalmente una palabra clave redundante en mi opinión ahora.

2: Volatile insinúa al compilador que la variable cambiará regularmente, por ejemplo, si define algún valor flotante como volátil:

volatile float flt;

Esto le dice al compilador que desea realizar una optimización específica para las variables que cambian regularmente. También establece que la variable puede cambiar sin la entrada del programa activo. Una vez más, una palabra clave principalmente redundante ahora (excepto en la programación de subprocesos múltiples).

3: Extern le dice al compilador que la definición está en otro archivo en el que se declaró la variable, por ejemplo, puede tener algún archivo de encabezado general, que incluye en la mayoría de sus archivos, aquí puede declarar algunos punteros globales, por lo que haría lo siguiente:

extern MyClass* g_pClassPointer;

Luego procedería a declarar en la parte superior del archivo cpp su MyClass se implementa en:

MyClass* g_pClassPointer = nullptr;

La palabra clave extern también se usa para declarar al compilador que está usando código C sin procesar o código ASM, por ejemplo, podría hacer lo siguiente:

extern __asm {
    mov eax, 2
    mov ebx, 3
    add eax, ebx
}

O si solo quisiera usar código C sin procesar, podría usar extern "C" y su compilador lo reconocerá.

4: Explícito es para usar cuando no desea una conversión implícita dentro de un constructor, para obtener más información, consulte este hilo. Se utiliza principalmente con fines de depuración y para garantizar que se obedezcan reglas más estrictas al realizar OOP.

  • -1: volátil no es una “pista”. Dice que “estos accesos deben ocurrir en el mismo orden y el mismo número de veces que he solicitado”. Se usa mucho en sistemas integrados de bajo nivel para hablar con hardware (así como en código de subprocesos múltiples, pero generalmente hay una mejor manera de comunicarse que a través de volátiles)

    – Martín Thompson

    28 de julio de 2011 a las 8:22

  • -1: volátil no es una “pista”. Dice que “estos accesos deben ocurrir en el mismo orden y el mismo número de veces que he solicitado”. Se usa mucho en sistemas integrados de bajo nivel para hablar con hardware (así como en código de subprocesos múltiples, pero generalmente hay una mejor manera de comunicarse que a través de volátiles)

    – Martín Thompson

    28 de julio de 2011 a las 8:22

¿Ha sido útil esta solución?