2009-01-17 34 views
35

Estoy trabajando con un código que usa ampliamente el modismo de devolver un puntero a una variable local estática. por ejemplo:¿Es seguro devolver un puntero a una variable local estática?

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

¿Estoy en lo cierto al pensar que esto es seguro?

PS, sé que esto sería una mejor forma de hacer la misma cosa:

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

Editar: Disculpas, la firma de la función debe ser, por supuesto:

const char* GetString(); 

Respuesta

32

Primer ejemplo:. Algo seguro

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

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

En particular, esta función no se considera una función reentrada porque las funciones de reentrada no debe, entre otras cosas, devolver la dirección de los datos estáticos (global) no constantes. Ver reentrant functions.

Segundo ejemplo: completamente inseguro

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

Esto sería seguro si lo hizo un const char *. Lo que diste no es seguro. La razón es porque 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:

usted debe considerar ya sea:

1) a continuación Si usted tiene acceso al código modificar el GetString tomar un parámetro de un char* buffer para llenar y una maxsize de usar.

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

6

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

+1

"En general, se debe evitar" podría ser demasiado fuerte, pero lo cierto es que debe ser consciente de los riesgos y limitaciones. +1 para mayor claridad en _why_ está bien. – dmckee

+0

Estoy de acuerdo con dmckee, los problemas de reentrada se deben al diseño de la estática que está activa en las llamadas a funciones. no es un mal comportamiento pero deberías ser consciente del riesgo, de hecho. –

0

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

Su sabia que hacer esto en los casos en los que desea:

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

Si my_string_to_error() devuelve una cadena asignada, su programa se filtraría dada la (muy) de uso común por encima de tal función.

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

... también está bien, algunos compiladores con muerte cerebral pueden querer que lo eluciden.

simplemente ver las cadenas de esta manera, no regrese un libro :)

2

Sí, es perfectamente seguro. La vida útil de las estáticas locales es la de la ejecución completa del programa en C. Así que puede devolver un puntero, ya que la matriz estará activa incluso después de que la función retorna, y el puntero devuelto puede ser desreferenciado válidamente.

9

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 se les permite 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 sencillo se muestra, no es necesario preocuparse por los desbordamientos de búfer, aunque mi versión del código no se preocupe, y asegura nula terminación. Una alternativa sería utilizar la función TR24731strcpy_s lugar:

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

Más importante aún, las dos variantes devolver un puntero (variable) para los datos constantes, por lo que el usuario no debe ir modificando la cadena y (probablemente) pisoteo fuera del rango de la matriz. (Como señala @strager en los comentarios, devolver un const char * no garantiza que el usuario no intente modificar los datos devueltos. Sin embargo, tienen que convertir el puntero devuelto para que no sea const y luego modificar los datos ; esto invoca un comportamiento indefinido y todo es posible en ese punto.)

Una ventaja del retorno literal es que la promesa de no escritura generalmente puede ser aplicada por el compilador y el sistema operativo. La cadena se colocará en el segmento de texto (código) del programa, y ​​el sistema operativo generará un error (violación de la segmentación en Unix) si el usuario intenta modificar los datos apuntados por el valor de retorno.

[Al menos una de las otras respuestas indica que el código no es reentrante; eso es correcto. La versión que devuelve el literal es reingresante. Si re-entrada es importante, la interfaz necesita ser arreglado de manera que la persona que llama proporciona el espacio donde se almacenan los datos]

+0

No es una promesa: es una sugerencia. Puedes soltar la const. Tiene razón en que probablemente debería ser const char * en lugar de char * const, pero no estoy seguro si el significado es diferente para los valores de retorno de la función. – strager

+0

@strager: sí, puede obligar al compilador a abusar de la devolución. En 'char * const', la const no tiene ningún beneficio; una vez que el valor se asigna a una variable de puntero separada (no constante), ese puntero puede modificarse; el valor de retorno nunca se pudo modificar. –

7

Depende de lo que quiera decir con seguridad. Hay un par de problemas que puedo ver de inmediato:

  1. Has vuelto un char * const, lo que permitirá a las personas que llaman a cambiar la cadena en este lugar. Desbordamiento del búfer potencial. ¿O quisiste decir un const char *?
  2. Es posible que tenga un problema con la reentrada o con la simultaneidad.

Para explicar la segunda, 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 llama 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)); 
} 

... lo que va a ser impreso?

Lo mismo para enhebrar.

+0

Buen ejemplo - ¡No he considerado esto! – strager

1

Es muy útil, ya que puede usar la función directamente como parámetro printf. Pero, como se mencionó, las llamadas múltiples a la función dentro de una sola llamada causarán un problema, porque la función usa el mismo almacenamiento y llamarlo dos veces sobrescribirá la cadena devuelta. Pero probé esta pieza de código y parece funcionar: se puede llamar de forma segura a una función, donde givemestring se usa a lo sumo MAX_CALLS veces 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 hilo de seguridad, pero esto se puede solucionar con las variables locales de rosca (palabra clave __thread de gcc)

Cuestiones relacionadas