punteros puntero parametros operadores lenguaje funciones direccion declarar con como aritmetica apuntadores c++ function-pointers language-lawyer one-definition-rule comdat-folding

c++ - operadores - punteros como parametros de funciones en c



¿Las distintas funciones tienen direcciones distintas? (4)

Considere estas dos funciones:

void foo() {} void bar() {}

¿Está garantizado que &foo != &bar ?

Similar,

template<class T> void foo() { }

¿Está garantizado que &foo<int> != &foo<double> ?

Hay dos enlazadores que conozco que combinan las definiciones de funciones.

MSVC agresivamente COMDAT pliega las funciones, por lo que dos funciones con la misma implementación se pueden convertir en una función. Como efecto secundario, las dos funciones comparten la misma dirección. Tenía la impresión de que esto era ilegal, pero no puedo encontrar el lugar en el estándar que se convierte en ilegal.

El enlazador Gold también pliega las funciones, con una configuración safe y all . safe significa que si se toma una dirección de función, no se pliega, mientras que all pliegan incluso si se toma la dirección. Entonces, doblar doblar safe comporta como si las funciones tienen direcciones distintas.

Si bien el plegado puede ser inesperado, y hay un código que depende de funciones distintas (implementación idéntica) que tienen direcciones diferentes (por lo que puede ser peligroso retirarse), ¿es realmente ilegal según el estándar actual de C ++? (C ++ 14 en este punto) (Naturalmente como si safe plegado safe es legal)


5.10 Operadores de igualdad [expr.eq]

1 El grupo de operadores == (igual a) y != (No igual a) de izquierda a derecha. Los operandos deben tener aritmética, enumeración, puntero o puntero a tipo de miembro o tipo std::nullptr_t . Los operadores == y != Ambos dan true o false , es decir, un resultado de tipo bool . En cada caso a continuación, los operandos tendrán el mismo tipo después de que se hayan aplicado las conversiones especificadas .
2 Si al menos uno de los operandos es un puntero, las conversiones de puntero (4.10) y las conversiones de calificación (4.4) se realizan en ambos operandos para llevarlos a su tipo de puntero compuesto (Cláusula 5). La comparación de punteros se define de la siguiente manera: Dos punteros se comparan iguales si ambos son nulos, ambos apuntan a la misma función, o ambos representan la misma dirección (3.9.2), de lo contrario, se comparan desiguales.

Tomemos el último bit por bit:

  1. Dos punteros nulos se comparan iguales.
    Bueno para tu cordura.
  2. Dos punteros a la misma función se comparan iguales.
    Cualquier otra cosa sería extremadamente sorprendente.
    También significa que solo una versión fuera de línea de cualquier función en inline puede tener su dirección tomada, a menos que desee que las comparaciones de punteros de función sean prohibitivamente complicadas y costosas.
  3. Ambos representan la misma dirección.
    Ahora ese es de lo que se trata. Dejar esto y reducir if and only if a un simple if lo deja a la interpretación, pero eso es un mandato claro para hacer dos funciones idénticas , siempre y cuando no cambie el comportamiento observable de un programa conforme.

Entonces, la parte problemática es claramente la frase o ambos representan la misma dirección (3.9.2) .

IMO esta parte está claramente allí para definir la semántica para los tipos de punteros a objetos. Y solo para tipos de punteros a objetos.

La frase hace referencia a la sección 3.9.2, lo que significa que debemos mirar allí. 3.9.2 conversaciones (entre otras) sobre las direcciones que representan los punteros de objeto. No habla de las direcciones que representan los punteros de función. Lo cual, IMO, deja solo dos posibles interpretaciones:

1) La frase simplemente no se aplica a los punteros de función. Lo cual deja solo los dos punteros nulos y dos punteros a la misma función comparando iguales, que es lo que probablemente la mayoría de nosotros esperábamos.

2) La frase se aplica. Dado que se está refiriendo a 3.9.2, que no dice nada sobre las direcciones que representan los punteros de función, podemos hacer que dos punteros a cada función se igualen. Lo cual es muy inesperado y, por supuesto, hace que la comparación de los punteros de función sea completamente inútil.

Entonces, aunque técnicamente se podría argumentar que (2) es una interpretación válida , IMO no es una interpretación significativa y, por lo tanto, debe descartarse. Y dado que no todos parecen estar de acuerdo en esto, también creo que se necesita una aclaración en el estándar.


Parece que el informe de defectos 1400: la igualdad del puntero a la función trata este problema y me parece que dice que está bien hacer esta optimización, pero como indican los comentarios, hay desacuerdo. Dice ( énfasis mío ):

De acuerdo con 5.10 [expr.eq], párrafo 2, dos punteros a función solo se comparan iguales si apuntan a la misma función . Sin embargo, como una optimización, las implementaciones actualmente son funciones de aliasing que tienen definiciones idénticas. No está claro si el estándar necesita tratar explícitamente esta optimización o no.

y la respuesta fue:

El estándar es claro en los requisitos, y las implementaciones son libres de optimizar dentro de las restricciones de la regla "como si" .

La pregunta es hacer dos preguntas:

  • ¿Está bien que estos indicadores se consideren iguales?
  • ¿Está bien fusionar las funciones?

En base a los comentarios, veo dos interpretaciones de la respuesta:

  1. Esta optimización está bien, el estándar otorga a la implementación esta libertad bajo la regla de si . La regla de si-si está cubierta en la sección 1.9 y significa que la implementación solo tiene que emular el comportamiento observable con respecto a los requisitos del estándar. Esta sigue siendo mi interpretación de la respuesta.

  2. El problema que tenemos entre manos es completamente ignorado y la afirmación simplemente dice que no se requiere ningún ajuste en el estándar porque claramente las reglas de si-si lo cubren pero la interpretación se deja como un ejercicio para el lector. Aunque reconozco que debido a la brevedad de la respuesta no puedo descartar este punto de vista, termina siendo una respuesta totalmente inútil. También parece inconsistente con las respuestas en las otras cuestiones NAD que, por lo que puedo decir, señalan el problema si existen.

Lo que dice el borrador del estándar

Como sabemos que estamos lidiando con la regla de si-si , podemos comenzar allí y observar que la sección 1.8 dice:

A menos que un objeto sea un subobjeto de clase de campo de bit o cero de tamaño cero, la dirección de ese objeto es la dirección del primer byte que ocupa. Dos objetos que no son campos de bits pueden tener la misma dirección si uno es un subobjeto de la otra, o si al menos uno es un subobjeto de clase base de tamaño cero y son de tipos diferentes; de lo contrario, deberán tener direcciones distintas. 4

y la nota 4 dice:

Bajo la regla "como si", una implementación puede almacenar dos objetos en la misma dirección de máquina o no almacenar un objeto en absoluto si el programa no puede observar la diferencia

pero una nota de esa sección dice:

Una función no es un objeto, independientemente de si ocupa o no el almacenamiento de la forma en que lo hacen los objetos

aunque no es normativo, los requisitos para un objeto establecido en el párrafo 1 no tienen sentido en el contexto de una función, por lo que es coherente con esta nota. Por lo tanto, estamos explícitamente restringidos de los objetos de aliasing con algunas excepciones, pero dicha restricción no se aplica a las funciones.

A continuación tenemos la sección 5.10 operadores de igualdad que dice ( énfasis mío ):

[...] Dos punteros se comparan iguales si ambos son nulos, ambos apuntan a la misma función, o ambos representan la misma dirección (3.9.2), de lo contrario, comparan desigual.

que nos dice que dos punteros son iguales si son:

  • Punteros nulos
  • Apunte a la misma función
  • Representar la misma dirección

El o ambos representan la misma dirección parece dar suficiente latitud para permitir que un compilador alía dos funciones diferentes y no requiere punteros a diferentes funciones para comparar desiguales.

Observaciones

Keith Thompson ha realizado algunas observaciones excelentes que creo que vale la pena agregar a la respuesta, ya que llegan a los principales problemas involucrados, dice:

Si un programa imprime el resultado de & foo == & bar , ese es un comportamiento observable; la optimización en cuestión cambia el comportamiento observable.

con lo cual estoy de acuerdo y si podemos mostrar que existe un requisito para que los indicadores sean desiguales que de hecho violarían la regla de si-si, pero hasta ahora no podemos mostrar eso.

y:

[...] considere un programa que define la función vacía y usa sus direcciones como valores únicos (piense en SIG_DFL , SIG_ERR y SIG_IGN en <signal.h> / <csignal> ). Asignarles la misma dirección rompería tal programa

Como señalé en mi comentario, el estándar C requiere que estas macros generen valores distintos , desde 7.14 en C11:

[...] que se expanden a expresiones constantes con valores distintos que tienen un tipo compatible con el segundo argumento para, y el valor de retorno de la función de señal, y cuyos valores se comparan desigualmente con la dirección de cualquier función declarable [...]

Entonces, aunque este caso está cubierto, quizás haya otros casos que podrían hacer que esta optimización sea peligrosa.

Actualizar

Jan Hubička, un desarrollador de gcc escribió una publicación de blog sobre el tiempo de enlace y las mejoras de optimización interprocedimiento en GCC 5 , el plegado de código fue uno de los muchos temas que cubrió.

Le pedí que comentara si el plegado de funciones idénticas a la misma dirección era el comportamiento conforme o no y él dice que no es un comportamiento conforme y, de hecho, tal optimización rompería el gcc sí mismo:

No es conveniente que dos funciones tengan la misma dirección, por lo que MSVC es bastante agresivo aquí. Al hacerlo, por ejemplo, se rompe el GCC en sí mismo porque, para mi sorpresa, la comparación de direcciones se realiza en el código de los encabezados precompilados. Funciona para muchos otros proyectos, incluido Firefox.

En retrospectiva, después de meses más leyendo informes de defectos y pensando en cuestiones de optimización, estoy predispuesto a una lectura más conservadora de la respuesta del comité. Tomar la dirección de una función es un comportamiento observable y, por lo tanto, plegar funciones idénticas violaría la regla de si .


Sí. Del estándar (§5.10 / 1): "Dos punteros del mismo tipo se comparan igual si y solo si son ambos nulos, ambos apuntan a la misma función, o ambos representan la misma dirección"

Una vez que se han instanciado, foo<int> y foo<double> son dos funciones diferentes, por lo que lo anterior también se aplica a ellas.