C Advertencia: la función devuelve la dirección de la variable local

10 minutos de lectura

avatar de usuario
franco vilea

La siguiente función toma el argv[0] argumento que contiene la ruta de llamada de la aplicación y reemplaza el último bit hasta que llega a “https://stackoverflow.com/” con el nombre de la nueva aplicación que quiero generar que se encuentra en la misma carpeta.

Por cierto: estoy declarando una variable argv global para que la función pueda tener acceso a ella porque no quería pasar la información en cada llamada de función.

Cuando compilo mi código, todo parece funcionar, pero recibo la advertencia anterior.

Sé que estoy declarando la variable y que tan pronto como la función regrese, será destruida.

Siendo un programador principiante de C, quería saber cuál sería la forma más elegante/más fácil de resolver este problema.

¿Debo pasar un puntero a la función o malloc algo de memoria?

char *returnFullPath()
{
    char pathToApp[strlen(argv[0])+1];
    strcpy(pathToApp, argv[0]);
    int path_length = strlen(argv[0]);

    while (pathToApp[path_length] != "https://stackoverflow.com/")
    {
        path_length--;
    }

    if (path_length > 2)
        pathToApp[path_length+1] = '\0';
    else
        pathToApp[0] = '\0';

    // length of getcwd + length of pathtoapp + 1 for zero plus 6 for "bidbot"
    char bidbotPath[strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6];

    sprintf(bidbotPath, "%s/%sbidbot", getcwd(NULL,0), pathToApp);

    return bidbotPath;
}

Algunas otras respuestas sugieren que compras algo y lo devuelves. Esta es una mala práctica en el mismo sentido que en C ++, cuando se crea algo nuevo en una función y se supone que la persona que llama debe eliminarlo (¿quién tiene la propiedad?)

Hay una razón por la que muchas API de C tienen el formato de:

function(buf, length);

Lo que significa que el LLAMADOR proporciona el búfer y cuánto dura. Es responsabilidad de la persona que llama asignar y desasignar este búfer y su función debe usarlo, y verificar que no va a desbordar la longitud.

No malloc y devolución. Es solo buscar problemas.

  • este buen consejo falla cuando no se puede esperar que la persona que llama sepa qué tamaño de búfer asignar.

    –David Heffernan

    1 de agosto de 2011 a las 12:18

  • Muchas bibliotecas y funciones de C, incluidas las de POSIX, asignan y devuelven precisamente por la razón que da David Heffernan. Mejor consejo: “no haga suposiciones sobre las convenciones de llamadas”, junto con “use un depurador de memoria”.

    – jmtd

    1 de agosto de 2011 a las 12:30

  • @David Heffernan, sí, lo sé, pero en el contexto de la pregunta original, MAX_PATH debería ser suficiente 🙂

    – Muu-jugo

    1 de agosto de 2011 a las 12:33


  • las rutas de Windows pueden ser más largas que MAX_PATH

    –David Heffernan

    1 de agosto de 2011 a las 12:41

  • @David: OK, puede haber excepciones, pero la respuesta anterior debería ser la regla general. Y en la mayoría de los casos las estrategias que mencioné funcionan. FWIW, si la función asigna, también debe saber el tamaño.

    – Rudy Velthuis

    1 de agosto de 2011 a las 14:25

Reemplazar

char bidbotPath[strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6];

con

char* bidbotPath = malloc(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6);

De esa manera, su variable se asigna en el montón y no en la pila, por lo que no se eliminará después de que regrese la función.

  • Él o ella pidió C, no C++.

    – Diego Sevilla

    1 de agosto de 2011 a las 11:45

  • Gracias Luchian, todavía estoy aprendiendo sobre malloc. Probé tu código y funciona. Esto es lo que creé previamente: “char * bidbotPath = malloc((sizeof(char))*(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6));” El tuyo se ve definitivamente más limpio. Pero, ¿cómo puede ser que no esté utilizando el operador sizeof? ¿Pensé que tiene que ser usado al mallocar? Gracias de nuevo por aclarar!!

    – Frank Vilea

    1 de agosto de 2011 a las 11:52

  • No utilicé sizeof porque el tamaño de un carácter SIEMPRE es 1. Esto está garantizado por el estándar. Por lo tanto, sería redundante decir malloc(sizeof(char)*(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6)).

    – Lucian Grigore

    1 de agosto de 2011 a las 11:54

  • Si está tratando de devolver una variable que está en el montón, ¿cómo puede liberar la variable del montón una vez que haya terminado con la función? Si la variable no se borra del montón, después de algunos bucles, el programa se congelará (si está trabajando en un microcontrolador).

    – Goutham Rapol

    11 de enero de 2021 a las 6:19

Si es posible, siempre es mejor que la función sea un puntero a la memoria en la que se pueda escribir el valor devuelto. Lo digo porque le permite a sus clientes (los que llaman a la función) la elección de dónde ubicar la memoria: en la pila o en el montón, o quizás incluso en algún lugar más exótico.

Ahora, el truco en esto es el si es posible cláusula. A veces, el tamaño de la memoria solo se puede determinar durante la implementación de la función. Un ejemplo típico sería una función con una cadena terminada en nulo. Cuando se encuentra con este escenario, normalmente es mejor recurrir a la asignación de memoria en el montón dentro de la función y solicitar a sus clientes que liberen la memoria cuando hayan terminado con ella.

Lo primero que debe decir es que la advertencia que está recibiendo podría considerarse un error. Cualquier llamada posterior a una nueva función inevitablemente escribirá sobre la memoria que contenía la información que pretendía devolver. Dicho esto, hay un par de maneras de solucionar este problema.

Propiedad del lado del cliente

Una podría ser, como sugirió Moo-Juice, agregar algunos parámetros a su invocación, delegando la responsabilidad de hacer que la información sea persistente después de la llamada a la función.

void returnFullPath(char* fullPath, int maxLength)

y antes de terminar, copie su resultado al parámetro de salida con una llamada a strncpy (http://www.cplusplus.com/reference/cstring/strncpy/).

strncpy(fullPath, bidbotPath, maxLength);

De esta forma, te aseguras de que la persona que llama a la función sea la propietaria de la memoria, asignándola y desasignándola. Y que no intentará usar memoria no asignada.

Propiedad del lado del proveedor

Sin embargo, existe otro enfoque, también aceptado para este lenguaje. Y es el que usa, por ejemplo, la librería stdio.h. Si desea abrir un archivo, utilice la estructura ARCHIVO, como puntero. En este caso, el stdio nos proporciona ambas funciones, fopen y fclose, una que asigna los recursos y otra que los desasigna. Esto hace uso de un concepto llamado tipo de datos abstractos, que es lo más parecido a un objeto que jamás veremos en la programación estructurada. Ver esta para obtener más detalles sobre los ADT. En este caso, un ADT completo parece una exageración absurda para lo que estás haciendo, pero va con la idea.

Para este caso, se requerirían ambas funciones, asignación y desasignación.

char* getFullPath(); /* here is where you do malloc*/
void disposeFullPath(char* fullPath); /* and here, free */

De esta manera, puede asignar la cantidad exacta de memoria que necesita


En relación con su pregunta, me gustaría hacer algunos comentarios.

  • Siempre que pueda, trate de cumplir con el estándar ANSI. Esta es wikipedia pero parece precisa.
  • Ahora que está utilizando C, debe comprobar las convenciones de estilo del lenguaje. Controlar esta.
  • Use strrchar para encontrar el último “https://stackoverflow.com/” en la ruta: aqui tienes
  • Y por último pero no menos importante: Evite las variables globales estáticas, no son más que dolores de cabeza

Cuando una función regresa, la variable local se liberará (desasignará) y la memoria se usará para otra cosa. Si devuelve la dirección de una variable local, puede (y debe) causar problemas.

Hay dos formas de resolver esto.

1) uso static variable. La variable local estática no se libera al salir de la función.

static char bidbotPath[....];

¡PERO! no funcionará con longitud variable.

2) Uso malloc

char *bidbotPath = malloc(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6);

y deberías llamar free(bidbotPath) después de todos los usos de la misma.

  • Un puntero a un static Se puede devolver la variable, pero el valor apuntado cambiará para todos los que usen un puntero de esa función cada vez que se llame a la función. Eso es muy raramente lo que quieres, y menos raramente lo que deberías querer.

    usuario395760

    1 de agosto de 2011 a las 11:51

  • @delnan, ¿está sugiriendo que sería mejor pasar un puntero a la función?

    – Frank Vilea

    1 de agosto de 2011 a las 11:53

  • @Frank: No, digo usando un static variable no es una solución. Pasar un puntero para que se rellene y devolver un puntero con una ubicación recién asignada son dos soluciones válidas, puede haber incluso más.

    usuario395760

    1 de agosto de 2011 a las 12:03

avatar de usuario
diego sevilla

tienes que asignar bidbotPath memoria variable dinámicamente usando malloc o calloc. Luego, asegúrese de que el código que llama a su función realmente esté liberando la memoria malloc que devuelve. Esta es una práctica común y un idioma común para las funciones de C que devuelven punteros a una matriz “generada”.

char * bidbotPath = (char*)malloc(strlen(getcwd(NULL,0)) + strlen(pathToApp) + 1 + 6);

  • Un puntero a un static Se puede devolver la variable, pero el valor apuntado cambiará para todos los que usen un puntero de esa función cada vez que se llame a la función. Eso es muy raramente lo que quieres, y menos raramente lo que deberías querer.

    usuario395760

    1 de agosto de 2011 a las 11:51

  • @delnan, ¿está sugiriendo que sería mejor pasar un puntero a la función?

    – Frank Vilea

    1 de agosto de 2011 a las 11:53

  • @Frank: No, digo usando un static variable no es una solución. Pasar un puntero para que se rellene y devolver un puntero con una ubicación recién asignada son dos soluciones válidas, puede haber incluso más.

    usuario395760

    1 de agosto de 2011 a las 12:03

Como BidotPath se declara dentro del alcance del cuerpo de la función como una variable de pila normal, desaparecerá cuando la función regrese. Si bien su programa podría funcionar ahora, es solo por suerte y puede comenzar a fallar más tarde si otro código reutiliza el área de pila anterior antes de que lo haga la persona que llama.

Puede declarar bobotPath static, lo que lo mantiene, pero evita que la función sea segura para subprocesos. Podría hacer un malloc de la longitud adecuada y devolverlo para mantener seguro el subproceso de la función, pero la persona que llama necesitaría liberar la memoria para evitar fugas. Lo mejor sería proporcionar una matriz de caracteres y una longitud para colocar los datos en el argumento de su función. Pensar snprintf() aquí. uso interior strncpy()y rutinas similares, para copiar en el destino, pero tenga en cuenta que strncat() puede que no sea muy seguro para usted.

Además, su código debe hacer frente al hecho de que es posible que no haya una barra inclinada en argv[0]… solo el nombre del ejecutable.

No es exactamente lo que necesita, pero aquí hay un código que he usado. Lo dejo como ejercicio para que el estudiante consiga lo que necesita:

  cp = strrchr( argv[0], "https://stackoverflow.com/" );
  if ( cp )
     cp++;
  else
    cp = argv[0];

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad