¿Cuál es la dirección de una función en un programa C++?


Ya que la función es un conjunto de instrucciones almacenadas en un bloque de memoria contiguo.

Y la dirección de una función (punto de entrada) es la dirección de la primera instrucción en la función. (de mi conocimiento)

Y así podemos decir que la dirección de la función y la dirección de la primera instrucción en la función será la misma (En este caso la primera instrucción es la inicialización de una variable.).

Pero el siguiente programa contradice lo anterior alinear.

Código:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
char ** fun()
{
    static char * z = (char*)"Merry Christmas :)";
    return &z;
}
int main()
{
    char ** ptr = NULL;

    char ** (*fun_ptr)(); //declaration of pointer to the function
    fun_ptr = &fun;

    ptr = fun();

    printf("\n %s \n Address of function = [%p]", *ptr, fun_ptr);
    printf("\n Address of first variable created in fun() = [%p]", (void*)ptr);
    cout<<endl;
    return 0;
}

Un ejemplo de salida es:

 Merry Christmas :) 
 Address of function = [0x400816]
 Address of first variable created in fun() = [0x600e10]

Por lo tanto, aquí la dirección de la función y la dirección de la primera variable en la función no es lo mismo. ¿Por qué es así?

He buscado en Google, pero no puede llegar a la respuesta exacta requerida y ser nuevo en este idioma que exactamente no puede coger algunos de los contenidos en la red.

Author: Housy, 2015-12-25

10 answers

Por lo tanto, aquí la dirección de la función y la dirección de la primera variable en la función no es lo mismo. ¿Por qué es así?

¿por Qué sería así? Un puntero de función es un puntero que apunta a la función. De todos modos, no apunta a la primera variable dentro de la función.

Para elaborar, una función (o subrutina) es una colección de instrucciones (incluida la definición de variables y diferentes instrucciones / operaciones) que realiza un trabajo específico, en su mayoría varias veces, según sea necesario. No es solo un puntero a los elementos presentes dentro de la función.

Las variables definidas dentro de la función no se almacenan en el mismo área de memoria que la del código máquina ejecutable. Basado en el tipo de almacenamiento, las variables que están presentes dentro de la función se encuentran en alguna otra parte de la memoria del programa de ejecución.

Cuando se construye un programa (compilado en un archivo objeto), diferentes partes del programa se organizan de una manera diferente.

  • Por lo general, la función (código ejecutable), reside en un segmento separado llamado segmento de código, generalmente una ubicación de memoria de solo lectura.

  • La variable tiempo de compilación asignado , OTOH, se almacena en el segmento de datos .

  • Las variables locales de la función, generalmente se rellenan en la memoria de la pila, como y cuando sea necesario.

Por lo tanto, no hay tal relación que una función puntero dará la dirección de la primera variable presente en la función, como se ve en el código fuente.

En este sentido, para citar el artículo de wiki ,

En lugar de referirse a valores de datos, un puntero de función apunta al código ejecutable dentro de la memoria.

Entonces, TL;DR, la dirección de una función es una ubicación de memoria dentro del segmento de código (texto) donde residen las instrucciones ejecutables.

 50
Author: Sourav Ghosh,
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-26 17:57:36

La dirección de una función es solo una forma simbólica de entregar esta función, como pasarla en una llamada o algo así. Potencialmente, el valor que obtiene para la dirección de una función ni siquiera es un puntero a la memoria.

Las direcciones de las funciones son buenas para exactamente dos cosas:

  1. Comparar para la igualdad p==q, y

  2. Para desreferenciar y llamar (*p)()

Cualquier otra cosa que intente hacer es indefinida, podría o no funcionar, y es del compilador decisión.

 17
Author: Aganju,
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-25 19:02:56

Bien, esto va a ser divertido. Podemos ir desde el concepto extremadamente abstracto de lo que es un puntero de función en C++ hasta el nivel de código ensamblador, y gracias a algunas de las confusiones particulares que estamos teniendo, ¡incluso podemos discutir las pilas!

Comencemos por el lado muy abstracto, porque ese es claramente el lado de las cosas desde el que estás empezando. tienes una función char** fun() con la que estás jugando. Ahora, en este nivel de abstracción, podemos ver lo que las operaciones están permitidas en punteros de función:

  • Podemos probar si dos punteros de función son iguales. Dos punteros de función son iguales si apuntan a la misma función.
  • Podemos hacer pruebas de desigualdad en esos punteros, lo que nos permite ordenar dichos punteros.
  • Podemos deferir un puntero de función, lo que resulta en un tipo de "función" con el que es realmente confuso trabajar, y elegiré ignorarlo por ahora.
  • Podemos "llamar" a un puntero de función, usando la notación que usaste: fun_ptr(). El significado de esto es idéntico a llamar a cualquier función a la que se esté apuntando.

Eso es todo lo que hacen a nivel abstracto. Debajo de eso, los compiladores son libres de implementarlo como mejor les parezca. Si un compilador quisiera tener un FunctionPtrType que es en realidad un índice en alguna tabla grande de cada función en el programa, podrían.

Sin embargo, esto no es típicamente cómo se implementa. Al compilar C++ hasta ensamblar / máquina código, tendemos a aprovechar tantos trucos específicos de la arquitectura como sea posible, para ahorrar tiempo de ejecución. En computadoras de la vida real, casi siempre hay una operación de "salto indirecto", que lee una variable (generalmente un registro), y salta para comenzar a ejecutar el código que está almacenado en esa dirección de memoria. Es casi universal que las funciones se compilan en bloques contiguos de instrucciones, por lo que si alguna vez salta a la primera instrucción en el bloque, tiene el efecto lógico de llamar a eso función. La dirección de la primera instrucción resulta satisfacer cada una de las comparaciones requeridas por el concepto abstracto de C++de un puntero de función y resulta ser exactamente el valor que el hardware necesita para usar un salto indirecto para llamar a la función! ¡Eso es tan conveniente, que prácticamente todos los compiladores eligen implementarlo de esa manera!

Sin embargo, cuando empezamos a hablar de por qué el puntero que pensabas que estabas mirando era el mismo que el puntero de la función, hay que entrar en algo un poco más matizado: segmentos.

Las variables estáticas se almacenan separadas del código. Hay algunas razones para eso. Una es que quieres tu código lo más ajustado posible. No querrás que tu código esté salpicado de espacios de memoria para almacenar variables. Sería ineficiente. Tendrías que pasar por alto todo tipo de cosas, en lugar de simplemente llegar a arar a través de ella. También hay una razón más moderna: la mayoría de las computadoras le permiten marcar alguna memoria como " ejecutable" y algunos " escribibles."Hacer esto ayuda tremendamente para lidiar con algunos trucos de hackers realmente malvados. Intentamos nunca marcar algo como ejecutable y escribible al mismo tiempo, en caso de que un hacker ingeniosamente encuentre una manera de engañar a nuestro programa para que sobreescriba algunas de nuestras funciones con las suyas propias.

En consecuencia, normalmente hay un segmento .code (usando esa notación punteada simplemente porque es una forma popular de notarla en muchas arquitecturas). En este segmento, se encuentran todos del código. Los datos estáticos entrarán en algún lugar como .bss. Así que puede encontrar su cadena estática almacenada bastante lejos del código que opera en ella (típicamente al menos 4kb de distancia, porque la mayoría del hardware moderno le permite establecer permisos de ejecución o escritura a nivel de página: las páginas son 4kb en muchos sistemas modernos)

Ahora la última pieza... pila. Mencionaste almacenar cosas en la pila de una manera confusa, lo que sugiere que puede ser útil darle un repaso rápido. Dejar me hacer una función recursiva rápida, porque son más eficaces en la demostración de lo que está pasando en la pila.

int fib(int x) {
    if (x == 0)
        return 0;

    if (x == 1)
        return 1;

    return fib(x-1)+fib(x-2);
}

Esta función calcula la secuencia de Fibonacci utilizando una forma bastante ineficiente pero clara de hacerlo.

Tenemos una función, fib. Esto significa que &fib es siempre un puntero al mismo lugar, pero claramente estamos llamando fib muchas veces, por lo que cada uno necesita su propio espacio ¿verdad?

En la pila tenemos lo que se llaman "marcos"."Los marcos son no las funciones en sí, sino que son secciones de memoria que esta invocación particular de la función puede usar. Cada vez que llamas a una función, como fib, asignas un poco más de espacio en la pila para su marco (o, más pedánticamente, lo asignará después de hacer la llamada).

En nuestro caso, fib(x) claramente necesita almacenar el resultado de fib(x-1) mientras ejecuta fib(x-2). No puede almacenar esto en la propia función, o incluso en el segmento .bss porque no sabemos cuántas veces se va a repetir. En su lugar, asigna espacio en la pila para almacenar su propia copia del resultado de fib(x-1) mientras fib(x-2) está operando en su propio marco (usando exactamente la misma función y la misma dirección de función). Cuando fib(x-2) regresa, fib(x) simplemente carga ese valor antiguo, que es seguro que no ha sido tocado por nadie más, agrega los resultados y lo devuelve!

¿Cómo hace esto? Prácticamente todos los procesadores que hay tienen algunos soporte para una pila en hardware. En x86, esto se conoce como el registro ESP (puntero de pila extendida). Los programas generalmente aceptan tratar esto como un puntero al siguiente punto en la pila donde puede comenzar a almacenar datos. Le invitamos a mover este puntero para construir espacio para un marco y moverse. Cuando termine de ejecutar, se espera que mueva todo hacia atrás.

De hecho, en la mayoría de las plataformas, la primera instrucción en su función es no la primera instrucción en la versión final compilada. Los compiladores inyectan algunas operaciones adicionales para administrar este puntero de pila para usted, de modo que ni siquiera tenga que preocuparse por ello. En algunas plataformas, como x86_64, este comportamiento es a menudo incluso obligatorio y especificado en el ABI!

Así que en todo tenemos: {[21]]}

  • .code segmento - donde se almacenan las instrucciones de su función. El puntero de la función apuntará a la primera instrucción aquí. Este segmento suele estar marcado como " ejecutar / solo lectura," evitar que su programa escriba en él después de que se haya cargado.
  • .bss segmento - donde se almacenarán sus datos estáticos, porque no puede ser parte del segmento "ejecutar solo" .code si quiere ser datos.
  • la pila - donde sus funciones pueden almacenar marcos, que realizan un seguimiento de los datos necesarios solo para esa instantación, y nada más. (La mayoría de las plataformas también usan esto para almacenar la información sobre dónde devolver a después de una función acabados)
  • el montón - Esto no apareció en esta respuesta, porque tu pregunta no incluye ninguna actividad de montón. Sin embargo, para completar, lo he dejado aquí para que no te sorprenda más tarde.
 10
Author: Cort Ammon,
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-26 19:18:46

En el texto de tu pregunta dices:

Y así podemos decir que la dirección de la función y la dirección de la primera instrucción en la función será la misma (En este caso la primera instrucción es la inicialización de una variable.).

Pero en el código no se obtiene la dirección de la primera instrucción en la función, sino la dirección de alguna variable local declarada en la función.

Una función es código, una variable es datos. Se almacenan en diferentes áreas de memoria; ni siquiera residen en el mismo bloque de memoria. Debido a las restricciones de seguridad impuestas por los sistemas operativos hoy en día, el código se almacena en bloques de memoria que están marcados como de solo lectura.

Por lo que sé, el lenguaje C no proporciona ninguna forma de obtener la dirección de una declaración en la memoria. Incluso si proporcionara tal mecanismo, el inicio de la función (la dirección de la función en memoria) no es la misma que la dirección del código máquina generado desde la primera C instrucción.

Antes del código generado a partir de la primera instrucción C, el compilador genera una función prolog que (al menos) guarda el valor actual del puntero de pila y deja espacio para las variables locales de la función. Esto significa varias instrucciones de ensamblado antes de cualquier código generado a partir de la primera instrucción de la función C.

 9
Author: axiac,
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-25 15:15:45

Como usted dice, la dirección de la función puede ser (depende del sistema) la dirección de la primera instrucción de la función.

Es la respuesta. La instrucción no compartirá la dirección con variables en un entorno típico en el que se usa el mismo espacio de direcciones para instrucciones y datos.

Si comparten la dirección samee, ¡la instrucción será destruida asignando a las variables!

 7
Author: MikeCAT,
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-25 14:26:08

¿Cuál es exactamente la dirección de una función en un programa C++?

Al igual que otras variables, una dirección de una función es el espacio asignado para ella. En otras palabras, es la ubicación de la memoria donde se almacenan las instrucciones (código máquina) para la operación realizada por la función.

Para entender esto, eche un vistazo profundo al diseño de memoria de un programa.

Las variables y el código/instrucciones ejecutables de un programa se almacenan en diferentes segmentos de memoria (RAM). Las variables van a cualquiera de los segmentos STACK, HEAP, DATA y BSS, mientras que el código ejecutable va al segmento CODE. Mira el diseño general de memoria de un programa

introduzca la descripción de la imagen aquí

Ahora puede ver que hay diferentes segmentos de memoria para variables e instrucciones. Se almacenan en diferentes ubicaciones de memoria. La dirección de la función es la dirección que se encuentra en el segmento de CÓDIGO.

Entonces, estás confundiendo el término primera declaración con la primera instrucción ejecutable. Cuando se invoca la llamada a la función, el contador de programa se actualiza con la dirección de la función. Por lo tanto, el puntero de función apunta a la primera instrucción de la función almacenada en la memoria.

introduzca la descripción de la imagen aquí

 7
Author: haccks,
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-27 09:50:26

La dirección de una función normal es donde comienzan las instrucciones (si no hay vTable involucrado).

Para las variables depende:

  • las variables estáticas se almacenan en otro lugar.
  • los parámetros se colocan en la pila o se mantienen en registros.
  • las variables localestambién se colocan en la pila o se mantienen en registros.

A menos que la función esté optimizado lejos.

 4
Author: Danny_ds,
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-25 15:30:15

Si no me equivoco, un programa se carga en dos ubicaciones de la memoria. El primero es el ejecutable de compilaciones que incluye funciones y variables predefinidas. Esto comienza con la memoria más baja que ocupa la aplicación. Con algunos sistemas operativos modernos, esto es 0x00000, ya que el administrador de memoria los traducirá según sea necesario. La segunda parte del código es el montón de aplicaciones es donde la fecha asignada en tiempo de ejecución, como los punteros residen, como cualquier memoria de tiempo de ejecución tendrá una ubicación diferente en memoria

 4
Author: Robert Humiston,
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-25 16:11:09

Otras respuestas aquí ya explican lo que es y no es un puntero de función. Voy a abordar específicamente por qué su prueba no prueba lo que pensó que hizo.

Y la dirección de una función (punto de entrada) es la dirección de la primera instrucción en la función. (de mi conocimiento)

Esto no es necesario (como explican otras respuestas), pero es común, y por lo general es una buena intuición también.

(En este caso la primera instrucción es la inicialización de un variable.).

Ok.

printf("\n Address of first variable created in fun() = [%p]", (void*)ptr);

Lo que estás imprimiendo aquí es la dirección de la variable. No la dirección de la instrucción que establece la variable.

Estos no son los mismos. De hecho, ellos no pueden ser lo mismo.

La dirección de la variable existe en una ejecución particular de la función. Si la función se llama varias veces durante la ejecución del programa, la variable puede estar en diferentes direcciones cada vez. Si la función llama recursivamente, o más generalmente si la función llama a otra función que llama that que llama a la función original, entonces cada invocación de la función tiene su propia variable, con su propia dirección. Lo mismo ocurre en un programa multiproceso si varios subprocesos están invocando esa función en un momento determinado.

Por el contrario, la dirección de la función es siempre la misma. Existe independientemente de si la función está siendo llamada actualmente: después de todo el punto de usar un el puntero de función suele llamar a la función. Llamar a la función varias veces no cambiará su dirección: cuando llamas a una función, no necesitas preocuparte si ya está siendo llamada.

Dado que la dirección de la función y la dirección de su primera variable tienen propiedades contradictorias, no pueden ser las mismas.

(Nota: es posible encontrar el sistema donde este programa podría imprimir los mismos dos números, aunque usted puede ir fácilmente a través de una carrera de programación sin encontrar uno. Existen arquitecturas de Harvard , donde el código y los datos se almacenan en diferentes memorias. En tales máquinas, el número cuando se imprime un puntero de función es una dirección en la memoria de código, y el número cuando se imprime un puntero de datos es una dirección en la memoria de datos. Los dos números podrían ser los mismos, pero sería una coincidencia, y en otra llamada a la misma función el puntero de la función sería el mismo pero la dirección de la variable podría cambio.)

 4
Author: Gilles,
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-27 10:50:02

Las variables declaradas dentro de una función no se asignan donde se ve en el código las variables automáticas (variables definidas localmente en una función) reciben un lugar adecuado en la memoria de la pila cuando la función está a punto de ser llamada , esto se hace durante el tiempo de compilación por el compilador, por lo tanto, la dirección de la primera instrucción no tiene nada que ver con variables se trata de las instrucciones ejecutables

 2
Author: Amir ElAttar,
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-27 09:45:11