valor referencia reales por parametros lenguaje funciones formales externas con c++ micro-optimization

c++ - referencia - ¿Se pasa realmente un parámetro sin nombre durante una llamada de función?



parametros formales y reales en c (4)

template <typename TAG> fn(int left, TAG, int right) { } fn(0, some_type_tag(), 1); /* or */ fn(0,int(), 1); // where the primitive, int, is not empty.

EDIT: Hay dos perspectivas para esta pregunta.

  1. Declaración de función vs definición. La declaración puede no nombrar el parámetro, pero la declaración sí lo hace. Esta no es la perspectiva de interés.
  2. Las perspectivas de la plantilla, espectativamente en la meta-programación. El parámetro en cuestión es una etiqueta utilizada para extraer una metaestructura de un rasgo. Esta es la razón por la que el parámetro no tiene nombre, solo me importa la información en tiempo de compilación, el tipo de etiqueta.

/EDITAR

Por lo general, mis etiquetas son estructuras vacías, sin embargo, en algunas partes de mi código son tipografías de tipos primitivos. Por lo tanto, me interesa saber si los compiladores modernos realmente pasarán un parámetro. Esto tiene dos aspectos.

  1. Tamaño de la pila, teniendo en cuenta el tamaño del tipo de parámetro sin nombre.
  2. Realmente construyendo la pila con el valor pasado.

Vamos a mantenerlo en gcc 4.5 y msvc 2008+


Buena pregunta, pero tendrás que probar en tu compilador. En teoría, si no se usa un parámetro, no tiene que asignarse en la pila. Sin embargo, las personas que llaman deben saber cómo llamarlo, así que supongo que el elemento se asigna realmente en la pila.


C ++ tiene traducción separada. Dado que el parámetro se puede nombrar en la declaración pero no en la definición de la función y viceversa, generalmente no hay forma de que el compilador sepa si es seguro omitir el argumento de la función. Cuando todo está en la misma unidad de traducción, todo podría estar en línea y el nombre del argumento es totalmente irrelevante para la optimización.

[Adicional]

La traducción por separado puede no ser importante para este caso específico, pero un generador de compiladores que agregue dicha optimización debe preocuparse. No pondrán tales optimizaciones si rompe el código perfectamente válido.

En cuanto a las plantillas, es necesario que el tipo de una función de plantilla sea igual al tipo de una función que no sea de plantilla, de lo contrario es imposible tomar su dirección y asignarla a un puntero de función. Una vez más, hay que tener en cuenta la traducción por separado. El hecho de que no tome la dirección de foo<int> en esta TU no significa que no lo hará en otra.


Es una pregunta bastante interesante en realidad.

En primer lugar, tenga en cuenta que estamos en un lenguaje imperativo, lo que significa que cuando solicita algo (incluso si es inútil, como construir un objeto no utilizado), el compilador debe cumplir, a menos que pueda encontrar una forma equivalente. Básicamente, podría ignorar el parámetro si pudiera probar que hacerlo no cambiaría el significado del programa.

Cuando escribes una llamada de función, pueden suceder dos cosas (al final):

  • o bien está en línea
  • o en realidad se emite una call

Si está en línea , entonces no se pasa ningún parámetro, lo que efectivamente significa que los objetos no utilizados pueden eliminarse (y ni siquiera construirse) si el compilador puede probar que los constructores y destructores involucrados no realizan ningún trabajo significativo. Funciona bien para estructuras de etiquetas.

Cuando se emite una llamada, se emite con una convención de llamada específica. Cada compilador tiene su propio conjunto de convenciones de llamada que especifican cómo pasar los diversos argumentos ( this puntero, etc.), generalmente tratando de aprovechar los registros disponibles.

Dado que solo la declaración de la función se utiliza para determinar la convención de llamada (modelo de compilación separado), entonces es necesario pasar el objeto ...

Sin embargo, si estamos hablando de una estructura vacía, sin método ni estado, entonces esto es solo una memoria sin inicializar. No debería costar mucho, pero requiere espacio de pila (al menos, reservándolo).

Demo usando la prueba llvm:

struct tag {}; inline int useless(int i, tag) { return i; } void use(tag); int main() { use(tag()); return useless(0, tag()); }

Da:

%struct.tag = type <{ i8 }> define i32 @main() { entry: ; allocate space on the stack for `tag` %0 = alloca %struct.tag, align 8 ; <%struct.tag*> [#uses=2] ; get %0 address %1 = getelementptr inbounds %struct.tag* %0, i64 0, i32 0 ; <i8*> [#uses=1] ; 0 initialize the space used for %0 store i8 0, i8* %1, align 8 ; call the use function and pass %0 by value call void @_Z3use3tag(%struct.tag* byval %0) ret i32 0 } declare void @_Z3use3tag(%struct.tag* byval)

Nota:

  • cómo se eliminó la llamada a useless y no se construye ningún argumento para ello
  • no se puede quitar la forma de llamar para use , y por lo tanto se asigna espacio para lo temporal (espero que las nuevas versiones no inicialicen en 0 la memoria)

Si el parámetro tiene nombre o no tiene ningún efecto en la firma de la función, y el compilador debe pasarlo. Tenga en cuenta que un parámetro sin nombre en la declaración de una función podría tener un nombre en la definición.

Ahora, en el caso particular de plantillas como la anterior, es probable que el compilador incorpore el código, en cuyo caso no se pasarán argumentos, y el argumento sin nombre no tendrá efecto.

Si lo que está tratando de hacer es etiquetar para resolver diferentes sobrecargas, siempre puede recurrir a un puntero, de modo que incluso si se pasa, el costo será mínimo.