2010-03-11 13 views
12

me encontré con algo de código legado que contiene una función como esta:¿No es este código heredado que devuelve una matriz de caracteres locales incorrecta?

LPCTSTR returnString() 
{ 
    char buffer[50000]; 
    LPCTSTR t; 

    /*Some code here that copies some string into buffer*/ 

    t = buffer; 
    return t; 
} 

Ahora, tengo la fuerte sospecha de que esto está mal. Intenté llamar a la función y devuelve la cadena que espera que devuelva. Sin embargo, realmente no veo cómo sucede eso: ¿no se supone que la matriz char está almacenada en la pila y, por lo tanto, desasignada después de que la función finaliza? Si me equivoco y se almacena en el montón, ¿no está creando esta función una pérdida de memoria?

+5

Sí, estás en lo correcto. No debe devolver una matriz estática de una función. – AraK

+1

@nvl Incluso si 't' se está asignando dinámicamente, se requeriría un operador de asignación sobrecargado para duplicar correctamente el búfer. – KevenK

+2

No es una pérdida de memoria, solo "suerte de que funcione". Si la memoria que la matriz asignada en la pila se reutiliza, entonces tendrá basura en su matriz. Es una buena forma de crear errores oscuros. –

Respuesta

8

Su código muestra un comportamiento indefinido; en este caso, el UB es que parece que "funciona". Si desea que la matriz esté almacenada en el montón, debe asignarla con la nueva []. La persona que llama de la función se encargará de borrarlo mediante el puntero que devuelve la función.

+3

Mejor aún, devuelva un 'std :: basic_string ' u objeto similar que sepa cómo copiar y desasignar los recursos que posee. –

3

Su sensación es correcta; el código está muy mal.

La memoria está efectivamente en la pila, y se va con el final de la función.

Si el búfer fuera estático, entonces al menos podría esperar que funcione para una llamada a la vez (en una aplicación de subproceso único).

5

Está en lo correcto, no se garantiza que funcione; y de hecho, si realmente almacena una cadena de 50,000 caracteres en este búfer, luego llama a alguna función (que llama a una función, que llama a una función ...) después de esto, en casi todos los sistemas esta cadena estará corrupta, debido a la función stack-frame siendo empujado a la pila.

La única razón por la que parece funcionar es que la memoria de la pila obsoleta (en la mayoría de los sistemas) no se borra después de que vuelve una función.

[Editar] Para aclarar, parece estar funcionando porque la pila no ha tenido la oportunidad de crecer 50,000 bytes. Intente cambiarlo a char buffer[10]; y llame a algunas funciones después de returnString(), y debería verlo dañado.

1

este código devuelve un puntero a la memoria asignada en la pila. es muy peligroso porque si intenta pasar este puntero a otra función, la memoria se sobrescribirá con la segunda llamada a la función.

en lugar de esto, se puede utilizar un buffer estático:

static char buffer[50000]; 

que no se asigna en la pila por lo que un puntero a ella sigue siendo válido. (Esto no es seguro para subprocesos, obviamente).

6

Sin pérdida de memoria, pero la función sigue siendo mal - la buffer se crea de hecho en la pila, y el hecho de que la persona que llama es capaz de leerlo es cuestión de suerte (que es accesible sólo justo después de la llamada a returnString() función.) Ese búfer puede ser sobrescrito por cualquier llamada de función u otra manipulación de pila.

La forma correcta de pasar datos por la cadena de llamadas es proporcionar un buffer y el tamaño a una función para llenar.

1

Tiene razón, el código es incorrecto. :)

Debe estudiar la asignación de memoria en C/C++. Los datos pueden residir en dos áreas: pila y pila.Las variables locales se almacenan en la pila. malloc ed y new los datos ed se almacenan en el montón. El estado de la pila es de funcionamiento local: las variables viven en el marco de la pila; el contenedor que se liberará cuando la función regrese. Entonces los indicadores se rompen.

El montón es global, por lo que todos los datos se almacenan hasta explícitamente delet ed o d por el programador. Puedes confiar en esa área.

1

Si bien todos piensan que el comportamiento no está definido, y en este caso parece ser cierto, en casos como este es importante considerar otras posibilidades.

Por ejemplo, una operator=(const char*) sobrecargada podría estar detrás de las escenas asignando la memoria de requisitos. Aunque este no es el caso (según mi leal saber y entender) con los typedefs de Microsoft, es importante tenerlo en cuenta en casos como este.

En este caso, sin embargo, parece ser conveniente que funcione, y ciertamente no es el comportamiento garantizado. Como otros han notado, esto realmente debería ser corregido.

+2

Esto no es posible: la función devuelve un carácter * (como un LPCSTR) y no puede sobrecargar operator =() para los punteros. –

+1

Soy consciente de que * en este caso * esto no es lo que está pasando. El punto era que en situaciones como esta (especialmente con asignaciones de objetos o clases de contenedor) es una característica importante a tener en cuenta al evaluar lo que está sucediendo. De lo contrario, lo que podría parecer una mala asignación de hecho puede ser completamente válido. Mi punto estaba totalmente dirigido a cómo examinar una situación como esta, y no tenía la intención de describir lo que está sucediendo * en este caso específico *. – KevenK

2

Hay muy poca diferencia entre una matriz y un puntero en C/C++. Entonces la declaración:

 
t = buffer; 

En realidad funciona porque "buffer" significa la dirección de la matriz. Sin embargo, la dirección no se almacena explícitamente en la memoria hasta que la coloca en t (es decir, el búfer no es un puntero). buffer [n] yt [n] harán referencia al mismo elemento de la matriz. Sin embargo, su matriz está asignada en la pila, por lo que la memoria se libera, no se borra, cuando la función vuelve. Si lo miras antes de que sea sobreescrito por otra cosa, aparecerá bien.

1

Este es un error peligroso que acecha en su código. En C y C++ no está permitido devolver un puntero para apilar datos en una función. Resulta en un comportamiento indefinido. Explicaré por qué.

Un programa C/C++ funciona presionando datos dentro y fuera de la pila de programas. Cuando llamas a una función, todos los parámetros se insertan en la pila y luego todas las variables locales también se envían a la pila. A medida que el programa se ejecuta, puede empujar y hacer estallar más elementos dentro y fuera de la pila en su función. En su ejemplo, el buffer se inserta en la pila, y luego se empuja t en la pila . La pila podría parecer,

  • Pila Anterior
  • Parámetros
  • (otros datos)
  • memoria intermedia (50000 bytes)
  • t (puntero sizeof)

En este punto , t está en la pila, y apunta al buffer, que también está en la pila. Cuando la función retorna, el tiempo de ejecución extrae todas las variables de la pila, que incluye t, el búfer y los parámetros. En su caso, devuelve el puntero t, por lo tanto haciendo una copia de él en la función de llamada.

Si la función de llamada y luego mira lo que t puntos a, se va a encontrar que que apunta a la memoria en la pila que puede o no existir.(El tiempo de ejecución lo sacó de la pila, pero los datos en la pila aún pueden estar allí por coincidencia, tal vez no).

La buena noticia es que no es inútil. Existen herramientas automatizadas que pueden buscar este tipo de errores en su software y reportarlos. Se llaman herramientas de análisis estáticas . Sentry es un ejemplo de un programa que puede informar este tipo de defecto .

1

Estoy de acuerdo en que lo más probable es que la dirección incorrecta de una variable automática sea incorrecta, sin embargo, echo eco a KevenK de que no se garantiza si es C++ como especifica la etiqueta. No sabemos qué es LPCTSTR. ¿Qué pasa si una cabecera incluida contiene algo como esto:

(sí sé que esto fugas, no es el punto)


class LPCTSTR{ 
private: 
    char * foo; 

public: 
    LPCTSTR & operator=(char * in){ 
    foo = strdup(in); 
    } 

    const char * getFoo(){ 
    return foo; 
    } 


}; 

+0

Bueno, si así fuera, el código aún no funcionaría. Necesitaría un constructor de copia definido por el usuario para copiar correctamente el valor LPCSTR de la función. –

+1

El punto no es cómo este ejemplo particular maneja la vida útil de la memoria. Es para demostrar que no se puede simplemente suponer que LPCTSTR es una macro para char *, como lo han hecho la mayoría de las respuestas upvoted. Existen implementaciones perfectamente válidas de una clase LPCTSTR que hacen que esta función se comporte de una manera bien definida. Si esto era C, claro. Sin embargo, la pregunta está etiquetada C++. – frankc

Cuestiones relacionadas