¿Es seguro devolver un puntero a una variable local estática?

6 minutos de lectura

avatar de usuario
John Carter

Estoy trabajando con un código que usa ampliamente el idioma de devolver un puntero a una variable local estática. p.ej:

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}

¿Tengo razón al pensar que esto es seguro?

PD, sé que esta sería una mejor manera de hacer lo mismo:

char* const GetString()
{
  return "Test";
}

Editar:
Disculpas, la firma de la función, por supuesto, debería ser:

const char* GetString();

avatar de usuario
Brian R. Bondy

Primer ejemplo: algo seguro

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}

Aunque no se recomienda, esto es seguro, el alcance de una variable estática permanece vivo incluso cuando finaliza el alcance de la función. Esta función no es muy segura para subprocesos en absoluto. Una mejor función le haría pasar un char* buffer y un maxsize Para el GetString() Función para llenar.

En particular, esta función no se considera una función reentrante porque las funciones reentrantes no deben, entre otras cosas, devolver la dirección a datos estáticos (globales) no constantes. Ver funciones reentrantes.

Segundo ejemplo: Completamente inseguro

char* const GetString()
{
  return "Test";
}

Esto sería seguro si hicieras un const char *. Lo que diste no es seguro. El motivo es que los literales de cadena se pueden almacenar en un segmento de memoria de solo lectura y permitir que se modifiquen generará resultados indefinidos.

char* const (puntero const) significa que no puede cambiar la dirección a la que apunta el puntero. const char * (puntero a const) significa que no puede cambiar los elementos a los que apunta este puntero.

Conclusión:

Debes considerar:

1) Si tiene acceso al código, modifique el GetString tomar un parámetro de un char* buffer para llenar y un maxsize usar.

2) Si no tiene acceso al código, pero debe llamarlo, envuelva este método en otra función que esté protegida por un mutex. El nuevo método es como se describe en 1.

avatar de usuario
extraño

static Las variables (en una función) son como variables globales con ámbito. En general, deben evitarse (al igual que las variables globales, causan problemas de reingreso), pero a veces son útiles (algunas funciones de biblioteca estándar las usan). Puede devolver punteros a variables globales, por lo que puede devolver punteros a static variables también.

  • “En general, deben evitarse” puede ser demasiado fuerte, pero es seguro que debe ser consciente de los riesgos y limitaciones. +1 para aclarar por qué está bien.

    – dmckee — gatito ex-moderador

    17 de enero de 2009 a las 18:15

  • Estoy de acuerdo con dmckee, los problemas de reingreso se deben al diseño de estática que está vivo en las llamadas a funciones. no es mal comportamiento. pero debe ser consciente del riesgo, de hecho.

    – Johannes Schaub – litb

    17 de enero de 2009 a las 18:18

Depende de lo que entiendas por seguro. Hay un par de problemas que puedo ver de inmediato:

  1. Has devuelto un char * const, que permitirá a las personas que llaman cambiar la cadena en esta ubicación. Potencial desbordamiento del búfer. ¿O quisiste decir un const char *?
  2. Es posible que tenga un problema con la reentrada o con la concurrencia.

Para explicar el segundo, considere esto:

const char * const format_error_message(int err)
{
    static char error_message[MAXLEN_ERROR_MESSAGE];
    sprintf(error_message, "Error %#x occurred", err);
    return error_message;
}

Si lo llamas así:

int a = do_something();
int b = do_something_else();

if (a != 0 && b != 0)
{
    fprintf(stderr,
        "do_something failed (%s) AND do_something_else failed (%s)\n",
        format_error_message(a), format_error_message(b));
} 

…¿Qué se va a imprimir?

Lo mismo para enhebrar.

avatar de usuario
jonathan leffler

Fundamentalmente, sí, es seguro en el sentido de que el valor durará indefinidamente porque es estático.

No es seguro en el sentido de que ha devuelto un puntero constante a datos variables, en lugar de un puntero variable a datos constantes. Es mejor si las funciones de llamada no pueden modificar los datos:

const char *GetString(void)
{
    static char sTest[5];
    strncpy(sTest, "Test", sizeof(sTest)-1);
    sTest[sizeof(sTest)-1] = '\0';
    return sTest;
}

En el caso simple que se muestra, no es necesario preocuparse por los desbordamientos del búfer, aunque mi versión del código sí se preocupa y garantiza una terminación nula. Una alternativa sería usar la función TR24731 strcpy_s en lugar de:

const char *GetString(void)
{
    static char sTest[5];
    strcpy_s(sTest, sizeof(sTest), "Test");
    return sTest;
}

Más importante aún, ambas variantes devuelven un puntero (variable) a datos constantes, por lo que el usuario no debe modificar la cadena y (probablemente) pisotear fuera del rango de la matriz. (Como @strager señala en los comentarios, devolver un const char * no es una garantía de que el usuario no intentará modificar los datos devueltos. Sin embargo, tienen que emitir el puntero devuelto para que no sea constante y luego modificar los datos; esto invoca un comportamiento indefinido y todo es posible en ese punto).

Una ventaja del retorno literal es que el compilador y el sistema operativo generalmente pueden hacer cumplir la promesa de no escribir. La cadena se colocará en el segmento de texto (código) del programa, y ​​el sistema operativo generará una falla (violación de segmentación en Unix) si el usuario intenta modificar los datos a los que apunta el valor devuelto.

[At least one of the other answers notes that the code is not re-entrant; that is correct. The version returning the literal is re-entrant. If re-entrancy is important, the interface needs to be fixed so that the caller provides the space where the data is stored.]

avatar de usuario
Johannes Schaub – litb

Sí, es perfectamente seguro. La vida útil de la estática local es la de la ejecución completa del programa en C. Por lo tanto, puede devolverle un puntero, ya que la matriz estará activa incluso después de que la función regrese, y el puntero devuelto puede desreferenciarse válidamente.

avatar de usuario
Nuclear

Es muy útil, ya que puede usar la función directamente como parámetro printf. Pero, como se mencionó, varias llamadas a la función dentro de una sola llamada causarán un problema, porque la función usa el mismo almacenamiento y llamarla dos veces sobrescribirá la cadena devuelta. Pero probé este fragmento de código y parece funcionar: puede llamar de forma segura a una función, donde se usa givemestring en la mayoría de las veces MAX_CALLS y se comportará correctamente.

#define MAX_CALLS 3
#define MAX_LEN 30

char *givemestring(int num)
{
        static char buf[MAX_CALLS][MAX_LEN];
        static int rotate=0;

        rotate++;
        rotate%=sizeof(buf)/sizeof(buf[0]);

        sprintf(buf[rotate],"%d",num);
        return buf[rotate];

}

El único problema es la seguridad de subprocesos, pero esto se puede resolver con variables locales de subprocesos (palabra clave __thread de gcc)

avatar de usuario
jonathan leffler

Sí, esto se usa con frecuencia para devolver la parte de texto de alguna búsqueda, es decir, para traducir algún número de error en una cadena amigable para los humanos.

Es aconsejable hacer esto en los casos en los que:

fprintf(stderr, "Error was %s\n", my_string_to_error(error_code));

Si my_string_to_error() devolvió una cadena asignada, su programa se filtraría dado el uso (muy) común anterior de dicha función.

char const *foo_error(...)
{
    return "Mary Poppins";
}

… también está bien, aunque algunos compiladores con muerte cerebral podrían querer que lo envíes.

Solo mire las cuerdas de esta manera, no devuelva un libro 🙂

¿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