He leído que convertir un puntero de función en un puntero de datos y viceversa funciona en la mayoría de las plataformas, pero no se garantiza que funcione. ¿Por qué es este el caso? ¿No deberían ser ambas direcciones simplemente en la memoria principal y, por lo tanto, ser compatibles?
c++
c
pointers
function-pointers
gexicida
fuente
fuente
void
. La conversión de un puntero de función avoid *
no alterará la representación. Unvoid *
valor resultante de dicha conversión se puede volver a convertir al tipo de puntero de función original, utilizando una conversión explícita, sin pérdida de información. Nota : El estándar ISO C no requiere esto, pero se requiere para la conformidad POSIX.dlsym()
: observe el final de la sección 'Uso de la aplicación' donde dice: Tenga en cuenta que la conversión de unvoid *
puntero a un puntero de función como en:fptr = (int (*)(int))dlsym(handle, "my_function");
no está definida por el estándar ISO C. Este estándar requiere que esta conversión funcione correctamente en implementaciones conformes.Respuestas:
Una arquitectura no tiene que almacenar código y datos en la misma memoria. Con una arquitectura de Harvard, el código y los datos se almacenan en una memoria completamente diferente. La mayoría de las arquitecturas son arquitecturas de Von Neumann con código y datos en la misma memoria, pero C no se limita solo a ciertos tipos de arquitecturas si es posible.
fuente
CS != DS
).VirtualProtect
, lo que le permite marcar regiones de datos como ejecutables.Algunas computadoras tienen (tenían) espacios de direcciones separados para código y datos. En tal hardware simplemente no funciona.
El lenguaje está diseñado no solo para las aplicaciones de escritorio actuales, sino para permitir que se implemente en un gran conjunto de hardware.
Parece que el comité del lenguaje C nunca tuvo la intención
void*
de ser un puntero para funcionar, solo querían un puntero genérico para los objetos.La justificación del C99 dice:
Nota No se dice nada sobre los punteros a las funciones en el último párrafo. Pueden ser diferentes de otros indicadores, y el comité lo sabe.
fuente
void *
.sizeof(void*) == sizeof( void(*)() )
desperdiciará espacio en el caso en que los punteros de función y los punteros de datos sean de diferentes tamaños. Este fue un caso común en los años 80, cuando se escribió el primer estándar C.Para aquellos que recuerdan MS-DOS, Windows 3.1 y versiones anteriores, la respuesta es bastante fácil. Todo esto solía admitir varios modelos de memoria diferentes, con diferentes combinaciones de características para punteros de código y datos.
Entonces, por ejemplo, para el modelo compacto (código pequeño, datos grandes):
y viceversa en el modelo Medio (código grande, datos pequeños):
En este caso, no tenía un almacenamiento separado para el código y la fecha, pero aún no podía convertir entre los dos punteros (salvo el uso de modificadores no estándar __near y __far).
Además, no hay garantía de que, incluso si los punteros son del mismo tamaño, apunten a lo mismo: en el modelo de memoria pequeña de DOS, tanto el código como los datos se usan cerca de los punteros, pero apuntan a diferentes segmentos. Por lo tanto, convertir un puntero de función en un puntero de datos no le daría un puntero que tuviera relación alguna con la función y, por lo tanto, no era útil para dicha conversión.
fuente
int*
a a levoid*
da un puntero con el que realmente no puede hacer nada, pero sigue siendo útil para poder realizar la conversión. (Esto se debe a quevoid*
puede almacenar cualquier puntero de objeto, por lo que puede usarse para algoritmos genéricos que no necesitan saber qué tipo contienen. Lo mismo podría ser útil también para los punteros de función, si estuviera permitido).int *
avoid *
,void *
se garantiza que al menos apunte al mismo objeto que el originalint *
, por lo que esto es útil para algoritmos genéricos que acceden al objeto señalado, comoint n; memcpy(&n, src, sizeof n);
. En el caso de que convertir un puntero de función en avoid *
no produzca un puntero apuntando a la función, no es útil para tales algoritmos; lo único que podría hacer es convertir elvoid *
puntero de nuevo a una función nuevamente, por lo que podría bueno, simplemente use un puntero queunion
contengavoid *
ay función.void*
hicieron punto a la función, supongo que sería una mala idea para las personas que pasan amemcpy
. :-Pvoid
. La conversión de un puntero de función avoid *
no alterará la representación. Unvoid *
valor resultante de tal conversión se puede volver a convertir al tipo de puntero de función original, utilizando una conversión explícita, sin pérdida de información. Nota : El estándar ISO C no requiere esto, pero se requiere para la conformidad POSIX.Se supone que los punteros a anular pueden acomodar un puntero a cualquier tipo de datos, pero no necesariamente un puntero a una función. Algunos sistemas tienen requisitos diferentes para punteros a funciones que punteros a datos (por ejemplo, hay DSP con diferente direccionamiento para datos frente a código, el modelo medio en MS-DOS utiliza punteros de 32 bits para código pero solo punteros de 16 bits para datos) .
fuente
Además de lo que ya se dijo aquí, es interesante mirar POSIX
dlsym()
:fuente
void*
.void*
ser compatible con un puntero de función, mientras que POSIX sí.C ++ 11 tiene una solución al desajuste de larga data entre C / C ++ y POSIX con respecto a
dlsym()
. Se puede usarreinterpret_cast
para convertir un puntero de función a / desde un puntero de datos siempre que la implementación admita esta característica.De la norma, 5.2.10 párr. 8, "la conversión de un puntero de función a un tipo de puntero de objeto o viceversa es compatible condicionalmente". 1.3.5 define "condicionalmente soportado" como una "construcción de programa que no es necesaria una implementación para soportar".
fuente
-Werror
). Una solución mejor (y no UB) es recuperar un puntero al objeto devuelto pordlsym
(es decirvoid**
) y convertirlo en un puntero para que funcione . Aún definido por la implementación, pero ya no causa una advertencia / error .dlsym
yGetProcAddress
compilar sin previo aviso.-pedantic
) no advierten. Nuevamente, no es posible especular.Dependiendo de la arquitectura de destino, el código y los datos pueden almacenarse en áreas de memoria fundamentalmente incompatibles y físicamente distintas.
fuente
void *
es lo suficientemente grande como para contener cualquier puntero de datos, pero no necesariamente ningún puntero de función.indefinido no necesariamente significa no permitido, puede significar que el implementador del compilador tiene más libertad para hacerlo como ellos quieran.
Por ejemplo, puede que no sea posible en algunas arquitecturas: indefinido les permite tener una biblioteca 'C' conforme, incluso si no puede hacerlo.
fuente
Otra solución:
Suponiendo que POSIX garantiza que los punteros de función y datos tengan el mismo tamaño y representación (no puedo encontrar el texto para esto, pero el ejemplo OP citado sugiere que al menos pretendieron hacer este requisito), lo siguiente debería funcionar:
Esto evita violar las reglas de alias al pasar por la
char []
representación, que permite alias de todos los tipos.Otro enfoque más:
Pero recomendaría el
memcpy
enfoque si desea una C 100% correcta.fuente
Pueden ser diferentes tipos con diferentes requisitos de espacio. Asignar a uno puede cortar irreversiblemente el valor del puntero para que la asignación de resultados resulte en algo diferente.
Creo que pueden ser de diferentes tipos porque el estándar no quiere limitar las posibles implementaciones que ahorran espacio cuando no es necesario o cuando el tamaño podría hacer que la CPU tenga que hacer más basura para usarlo, etc.
fuente
La única solución verdaderamente portátil es no usar
dlsym
para funciones, y en su lugar usardlsym
para obtener un puntero a datos que contengan punteros de función. Por ejemplo, en tu biblioteca:y luego en su aplicación:
Por cierto, esta es una buena práctica de diseño de todos modos, y hace que sea fácil admitir la carga dinámica a través
dlopen
y la vinculación estática de todos los módulos en sistemas que no admiten la vinculación dinámica, o donde el usuario / integrador del sistema no quiere utilizar la vinculación dinámica.fuente
foo_module
estructura (con nombres únicos), simplemente puede crear un archivo adicional con una matrizstruct { const char *module_name; const struct module *module_funcs; }
y una función simple para buscar en esta tabla el módulo que desea "cargar" y devolver el puntero correcto, luego use esto en lugar dedlopen
ydlsym
.Un ejemplo moderno de dónde los punteros de función pueden diferir en tamaño de los punteros de datos: punteros de función miembro de clase C ++
Citado directamente de https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/
tl; dr: cuando se usa la herencia múltiple, un puntero a una función miembro puede (dependiendo del compilador, versión, arquitectura, etc.) realmente almacenarse como
que obviamente es más grande que a
void *
.fuente
En la mayoría de las arquitecturas, los punteros a todos los tipos de datos normales tienen la misma representación, por lo que la conversión entre tipos de punteros de datos no es una opción.
Sin embargo, es concebible que los punteros de función requieran una representación diferente, tal vez sean más grandes que otros punteros. Si void * pudiera contener punteros de función, esto significaría que la representación de void * tendría que ser de mayor tamaño. Y todos los moldes de punteros de datos a / desde void * tendrían que realizar esta copia adicional.
Como alguien mencionó, si necesita esto, puede lograrlo utilizando una unión. Pero la mayoría de los usos de void * son solo para datos, por lo que sería oneroso aumentar todo el uso de memoria en caso de que sea necesario almacenar un puntero de función.
fuente
Sé que esto no ha sido comentada desde 2012, pero pensé que sería útil añadir que yo hago conocer una arquitectura que tiene muy punteros incompatibles para datos y funciones desde una llamada en la que la arquitectura cheques privilegio y lleva la información adicional. Ninguna cantidad de casting ayudará. Es el molino .
fuente