¿Por qué los punteros de función y los punteros de datos son incompatibles en C/C++?


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 ambas ser simplemente direcciones en la memoria principal y por lo tanto ser compatibles?

Author: Smi, 2012-09-11

14 answers

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.

 168
Author: Dirk Holsopple,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 20:26:03

Algunas computadoras tienen (tenían) espacios de direcciones separados para el código y los 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 de void* de ser un puntero a la función, solo querían un puntero genérico a los objetos.

La Justificación de C99 dice:

6.3.2.3 Punteros
C se ha implementado en una amplia gama de arquitecturas. Mientras que algunos de estos las arquitecturas cuentan con punteros uniformes que son del tamaño de algún tipo entero, máximo el código portable no puede asumir ninguna correspondencia necesaria entre los diferentes tipos de puntero y los tipos enteros. En algunas implementaciones, los punteros pueden incluso ser más anchos que cualquier tipo entero.

El uso de void* ("puntero a void") como un tipo de puntero de objeto genérico es una invención del C89 Comité. La adopción de este tipo fue estimulada por el deseo de especificar argumentos de prototipo de función que, o bien convierten silenciosamente punteros arbitrarios (como en fread) o se quejan si el tipo de argumento no coincide exactamente (como en strcmp). No se dice nada acerca de los punteros a funciones, que pueden ser inconmensurables con los punteros de objetos y / o enteros.

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é es consciente de ello.

 37
Author: Bo Persson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-11 06:35:40

Para aquellos que recuerdan MS-DOS, Windows 3.1 y anteriores, la respuesta es bastante fácil. Todos estos se utilizan para soportar varios modelos de memoria diferentes, con diferentes combinaciones de características para punteros de código y datos.

Así, por ejemplo, para el modelo compacto (código pequeño, datos grandes):

sizeof(void *) > sizeof(void(*)())

E inversamente en el modelo Mediano (código grande, datos pequeños):

sizeof(void *) < sizeof(void(*)())

En este caso, no tenía almacenamiento separado para el código y la fecha, pero aún no podía convertir entre dos punteros (menos que usar modificadores __near y __far no estándar).

Además, no hay garantía de que incluso si los punteros son del mismo tamaño, que apuntan a la misma cosa - en el modelo de memoria pequeña de DOS, tanto el código como los datos utilizados cerca de punteros, pero apuntaban a diferentes segmentos. Así que convertir un puntero de función a un puntero de datos no le daría un puntero que tenía ninguna relación con la función en absoluto, y por lo tanto no había uso para tal conversión.

 30
Author: Tomek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-11 00:54:23

Se supone que los punteros a void pueden acomodar un puntero a cualquier tipo de datos but pero no necesariamente un puntero a una función. Algunos sistemas tienen requisitos diferentes para los punteros a funciones que los punteros a datos (por ejemplo, hay DSP con direccionamiento diferente para datos vs.código, el modelo medio en MS-DOS utiliza punteros de 32 bits para el código, pero solo punteros de 16 bits para los datos).

 23
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-07 18:00:56

Además de lo que ya se ha dicho aquí, es interesante mirar POSIX dlsym():

El estándar ISO C no requiere que los punteros a funciones se puedan convertir de ida y vuelta a punteros a datos. De hecho, el estándar ISO C no requiere que un objeto de tipo void * pueda contener un puntero a una función. Sin embargo, las implementaciones que soportan la extensión XSI requieren que un objeto de tipo void * pueda contener un puntero a una función. El resultado de la conversión de un sin embargo, el puntero a una función en un puntero a otro tipo de datos (excepto void *) sigue sin definirse. Tenga en cuenta que los compiladores que cumplen con el estándar ISO C deben generar una advertencia si se intenta una conversión de un puntero void * a un puntero de función como en:

 fptr = (int (*)(int))dlsym(handle, "my_function");

Debido al problema señalado aquí, una versión futura puede agregar una nueva función para devolver punteros de función, o la interfaz actual puede ser obsoleta en favor de dos nuevas funciones: una que devuelve datos punteros y el otro que devuelve punteros de función.

 12
Author: Maxim Egorushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 20:38:34

C++11 tiene una solución al desajuste de larga data entre C/C++ y POSIX con respecto a dlsym(). Se puede usar reinterpret_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 se admite condicionalmente."1.3.5 define " soportado condicionalmente"como una" construcción de programa que una implementación no necesita soportar".

 9
Author: David Hammen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 23:01:30

Dependiendo de la arquitectura de destino, el código y los datos pueden almacenarse en áreas de memoria fundamentalmente incompatibles y físicamente distintas.

 7
Author: Graham Borland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-07 18:00:21

Indefinido no significa necesariamente no permitido, puede significar que el implementador del compilador tiene más libertad para hacerlo como quiera.

Por ejemplo, puede que no sea posible en algunas arquitecturas - undefined les permite seguir teniendo una biblioteca 'C' conforme, incluso si no puede hacer esto.

 5
Author: Martin Beckett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-07 18:00:58

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 en algo diferente.

Creo que pueden ser tipos diferentes 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 basura extra para usarlo, etc...

 5
Author: Crazy Eddie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 20:35:22

Otra solución:

Asumiendo 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 intentaron hacer este requisito), lo siguiente debería funcionar:

double (*cosine)(double);
void *tmp;
handle = dlopen("libm.so", RTLD_LAZY);
tmp = dlsym(handle, "cos");
memcpy(&cosine, &tmp, sizeof cosine);

Esto evita violar las reglas de aliasing pasando por la representación char [], que se permite alias de todos los tipos.

Otro enfoque:

union {
    double (*fptr)(double);
    void *dptr;
} u;
u.dptr = dlsym(handle, "cos");
cosine = u.fptr;

, Pero yo recomendaría el enfoque memcpy si desea absolutamente 100% correcto C.

 4
Author: R..,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-07 20:16:56

La única solución realmente portátil es no usar dlsym para funciones, y en su lugar usar dlsym para obtener un puntero a datos que contenga punteros de función. Por ejemplo, en su biblioteca:

struct module foo_module = {
    .create = create_func,
    .destroy = destroy_func,
    .write = write_func,
    /* ... */
};

Y luego en su solicitud:

struct module *foo = dlsym(handle, "foo_module");
foo->create(/*...*/);
/* ... */

Por cierto, esta es una buena práctica de diseño de todos modos, y hace que sea fácil admitir tanto la carga dinámica a través de dlopen como 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 de sistemas no desea utilice enlaces dinámicos.

 2
Author: R..,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-07 19:11:30

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 puntero de datos no es operativa.

Sin embargo, es concebible que los punteros de función puedan requerir 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 lanzamientos de punteros de datos a/desde void * tendrían que realizar este extra copia.

Como alguien mencionó, si necesitas esto puedes lograrlo usando una unión. Pero la mayoría de los usos de void * son solo para datos, por lo que sería oneroso aumentar todo su uso de memoria en caso de que se necesite almacenar un puntero de función.

 2
Author: Barmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-11 21:18:54

Un ejemplo moderno de dónde los punteros de función pueden diferir en tamaño de los punteros de datos: C++ class member function pointers

Citado Directamente de https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/

class Base1 { int b1; void Base1Method(); };
class Base2 { int b2; void Base2Method(); };
class Derived : public Base1, Base2 { int d; void DerivedMethod(); };

Ahora Hay dos posibles this punteros.

Un puntero a una función miembro de Base1 se puede utilizar como un puntero a una función miembro de Derived, ya que ambos usan la misma this puntero. Pero un no se puede utilizar el puntero a una función miembro de Base2 as-is como un puntero a una función miembro de Derived, ya que el this el puntero necesita ser ajustado.

Hay muchas maneras de resolver esto. Así es como el Visual Studio el compilador decide manejarlo:

Un puntero a una función miembro de una clase de herencia múltiple es realmente estructura.

[Address of function]
[Adjustor]

El tamaño de una función puntero a miembro de una clase que usa herencia múltiple es el tamaño de una puntero más el tamaño de un size_t.

Tl; dr: Cuando se usa herencia múltiple, un puntero a una función miembro puede (dependiendo del compilador, versión, arquitectura, etc.) almacenarse como

struct { 
    void * func;
    size_t offset;
}

Que es obviamente más grande que un void *.

 0
Author: Andrew Sun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-27 05:46:22

Sé que esto no se ha comentado desde 2012, pero pensé que sería útil agregar que yo conozco una arquitectura que tiene punteros muy incompatibles para datos y funciones, ya que una llamada a esa arquitectura comprueba el privilegio y lleva información adicional. Ninguna cantidad de fundición ayudará. Es El Molino.

 -1
Author: Patrick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-11 18:27:39