2011-12-30 15 views
10

Estoy tratando de entender cómo funciona devolviendo un puntero en los siguientes escenarios:Tratar de entender cómo funciona un tipo de retorno puntero

#include <iostream> 
using namespace std; 

// Why does this work? I can even pass the return value to another function 
// and the contents do not change. 
char* StringFromFunction() 
{ 
    char* pReturn = "This string was created in the function."; 
    return pReturn; 
} 

// I know this is wrong because the memory address where 5 is stored can be 
// overwritten. 
int* IntegerFromFunction() 
{ 
    int returnValue = 5; 
    return &returnValue; 
} 

int main() 
{ 
    int* pInteger; 
    char* pString; 

    pString = StringFromFunction(); 
    pInteger = IntegerFromFunction(); 

    cout << *pInteger << endl << pString << endl; 

    return 0; 
} 

La salida del programa es como yo esperaba que fuera:

5 
This string was created in the function. 

La única advertencia del compilador que recibo en Visual C++ 2010 Express es "c: \ vc2010projects \ test \ main.cpp (14): advertencia C4172: devolución de la dirección de la variable local o temporal" y solo se muestra cuando uso IntegerFromFunction() y no StringFromFunction().

Lo que creo que estoy entendiendo a partir de los ejemplos anteriores es el siguiente:

Dentro StringFromFunction(), la asignación de memoria para el texto "Esta cadena fue creado en la función." ocurre en el momento de la ejecución y porque es un literal de cadena, el contenido persiste en la memoria incluso después de que la función retorna, y es por eso que el puntero pString en main() se puede pasar a otra función y la cadena se puede mostrar dentro de ella.

Sin embargo, para el IntegerFromFunction(), cuando la función retorna, la memoria asignada se libera y, por lo tanto, esa dirección de memoria se puede sobrescribir.

Supongo que mi pregunta principal es, ¿pueden los punteros que apuntan a los literales de cadena pasar con seguridad a través del programa?

+0

intente también: char * StringFromFunction() { char [] pReturn = "Esta cadena se ha creado en la función."; return pReturn; } – stmax

Respuesta

3

La forma más fácil de ver la diferencia es generar la Desensamblaje de un ejemplo simple hola-mundo-ish:

char* test() { 
return "Test"; 
} 

int main(int argc, char* argv[]) { 
return 0; 
} 

Este es el desensamblado con gcc en FreeBSD con la optimización de apagado

.file "hellow.c" 
    .section .rodata 
.LC0: 
    .string "test" 
    .text 
    .p2align 4,,15 
.globl test 
    .type test, @function 
test: 
    pushl %ebp 
    movl %esp, %ebp 
    movl $.LC0, %eax 
    popl %ebp 
    ret 
    .size test, .-test 
    .p2align 4,,15 
.globl main 
    .type main, @function 
main: 
    leal 4(%esp), %ecx 
    andl $-16, %esp 
    pushl -4(%ecx) 
    pushl %ebp 
    movl %esp, %ebp 
    pushl %ecx 
    call test 
    movl $0, %eax 
    popl %ecx 
    popl %ebp 
    leal -4(%ecx), %esp 
    ret 
    .size main, .-main 
    .ident "GCC: (GNU) 4.2.1 20070719 [FreeBSD]" 

Como puede ver, el literal de la cadena se almacenó en la sección .LC0, no en el código en sí. La función de prueba simplemente devuelve un puntero al comienzo de .LC0 (movl $ .LC0,% eax) ya que este es el primer literal de cadena. La ubicación es similar (pero no igual) dependiendo del formato ejecutable al que está compilando. Lea A.out (segmento de texto) o PE.

+0

Esto podría ser interesante también, pero un poco más avanzado: http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/dataseg.html – WebMonster

+0

Gracias por esto. Me acabas de ayudar a entender muchas de las respuestas. Por ejemplo, ahora sé a qué se refiere rodata, y ahora puedo ver cómo hay un área en el ejecutable donde los literales de cadena están realmente almacenados y la función en realidad no creó el literal de cadena. Esto me lleva a otra pregunta. ¿Esto significa que puedo acceder al literal de la cadena sin llamar nunca a la función? –

+0

Sí, puede en el mismo sentido en que puede acceder a cualquier variable global sin tener que hacer referencia alguna (entrarían en el segmento .data) sin optimización. No estoy seguro de que el compilador no los optimice si está habilitado, ya que no incluiría la función de prueba si no se llamaba alguna vez. Sin embargo, sería difícil acceder a él, ya que no tiene un puntero. Necesitaría algunos trucos sucios o ensamblaje en línea. – WebMonster

1

Sí, es posible pasar punteros a literales de cadena, siempre y cuando solo lea de ellos. Esto se debe a que, básicamente, se asignan estáticamente por el compilador, especie de la siguiente manera:

/* global/static variables go in the data section at compile time, not in the stack*/ 
char myString[6] = {'a', ' ', 'l', 'i', 't', '\0'}; 

char* StringFromFunction() 
{ 
    char* pReturn = &myString[0]; //this pointer is not actually to inside this function! 
    return pReturn; 
} 

Si quiere volar las cosas intentar asignar una matriz real de la función en lugar de obtener un puntero a la cadena literal.

char* StringFromFunction() 
{ 
    char myString[6] = {'a', ' ', 'l', 'i', 't', '\0'}; 
    return &myString[0]; 
} 

tenga en cuenta que los literales de cadena son constantes y sólo lectura. No intente escribir o actualizarlos, para no caer en un comportamiento indefinido.

1

Los literales de cadena como "This string was created in the function." se colocan en la memoria de solo lectura. Solo se le permite asignarlos a un char * por compatibilidad con versiones anteriores, es más apropiado usar const char * que refleje con precisión su naturaleza.

4

Los literales de cadena no se almacenan realmente en la pila para la función como las variables automáticas, pero se almacenan en una ubicación especial (como variables globales).

Tenga en cuenta que escribir en ellos no es portátil, por lo que es mejor usarlos como const char * y no como char *.

+0

¿Esta "ubicación especial" forma parte del montón, o está separada de la pila/montón por completo? – The111

+1

@ The111 Seperate. Es una sección especial en el ejecutable, como la sección de códigos. En archivos ELF (utilizados en Linux y otros lugares), se llama '.rodata'. Vea la respuesta de sarnold para más información. – cha0site

+0

@ The111 Generalmente se almacenan en la sección de solo lectura del ejecutable. – Marlon

3

Cuando puedo compilar su programa, me sale una advertencia adicional de g++, mi compilador:

$ make strings 
g++  strings.cc -o strings 
strings.cc: In function ‘char* StringFromFunction()’: 
strings.cc:8:19: warning: deprecated conversion from string constant to ‘char*’ 
strings.cc: In function ‘int* IntegerFromFunction()’: 
strings.cc:16:7: warning: address of local variable ‘returnValue’ returned 

recibir ningún aviso, añadir const frente a la declaración de la variable, el tipo de retorno de la función y la variable en la función main().

La cadena de herramientas GNU va a almacenar la cadena This string was created in the function. en el de sólo lectura sección .rodata datos, que es válida para toda la vida del programa:

$ readelf -p .rodata strings 

String dump of section '.rodata': 
    [  8] This string was created in the function. 

Por supuesto, no puede modificar el contenido de la cadena, pero eso generalmente está bien para cadenas compiladas estáticamente en el programa.

2

La clave para comprender es lifetime del objeto al que está devolviendo un puntero (en realidad, la duración del objeto es una cuestión clave para comprender casi cualquier instancia de objeto). el estándar C usa la terminología de 'duración de almacenamiento' para la duración del objeto, ya que en C un objeto es literalmente una región de almacenamiento de datos que representa valores.

Una cadena literal tiene 'duración estática de almacenamiento', lo que significa (C99 6.2.4/3):

su vida útil es toda la ejecución del programa y se inicializa su valor almacenado sólo una vez, antes para iniciar el programa.

Así que no hay ningún problema para devolver un puntero a un literal de una función (en lo que respecta a la duración del objeto al que hace referencia el puntero). El objeto literal de cadena siempre será un objeto válido. Una cosa a tener en cuenta es que el puntero devuelto permitirá que alguien intente modificar la matriz de datos que contiene el literal de cadena, lo cual no está permitido (es un comportamiento indefinido).

El int returnValue variable local en el otro ejemplo tiene 'duración automático de almacenamiento' que significa (C99 6.2.4/4):

su vida útil se extiende desde la entrada en el bloque con el que está asociado hasta que la ejecución de ese bloque termina de alguna manera

(tenga en cuenta que el tiempo de vida de una matriz de longitud variable automática es ligeramente diferente).

Por lo tanto, el puntero a returnValue deja de ser válido en el momento en que se devuelve la función.

Creo que la duración del objeto es una de las cosas fundamentales que todo programador debe entender, y es particularmente importante en C y C++ ya que el programador es responsable de manejarlo correctamente, especialmente cuando se trata de punteros.

+0

+1 para describir correctamente _por qué_ el comportamiento es como es en vez de simplemente diseccionar _ lo que es el comportamiento de alguna implementación arbitraria. – ildjarn

Cuestiones relacionadas