"Vida útil" de un literal de cadena en C


84

¿No sería inaccesible el puntero devuelto por la siguiente función?

char *foo(int rc)
{
    switch (rc)
    {
        case 1:

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    }
}

Entonces, la vida útil de una variable local en C / C ++ es prácticamente solo dentro de la función, ¿verdad? Lo que significa que, después de que char* foo(int)termina, el puntero que devuelve ya no significa nada, ¿verdad?

Estoy un poco confundido acerca de la vida útil de una variable local. ¿Qué es una buena aclaración?


10
La única "var" que tiene en su función es el parámetro int rc. Su vida finaliza en cada una de las return-s. Los punteros que está devolviendo son a cadenas literales. Los literales de cadena tienen una duración de almacenamiento estática: su vida útil es al menos tan larga como la del programa.
Kaz

14
@PedroAlves ¿Por qué no? Los métodos permiten la abstracción; ¿Qué pasa si en el futuro la cadena se leerá de un recurso de traducción, pero no se necesita soporte de internacionalización para V1 (o V0.5) de un producto?
dlev

1
@PedroAlves " Seguro que tu código funcionará (y puedes verlo si intentas compilarlo)" . Muchos (la mayoría? ¿Esencialmente todos?) Compiladores de c consumirán código ilegal y, a menudo, emitirán código que parece funcionar. Pero inténtelo en otro compilador (o incluso en una versión diferente del mismo compilador) y puede caerse.
dmckee --- ex-moderador gatito

6
@PedroAlves, una función que devuelve una sola cadena constante puede ser de uso limitado, pero ¿qué tal una que devuelve cualquiera de varias cadenas constantes dependiendo de la entrada o el estado del objeto? Un ejemplo simple sería una función para convertir una enumeración en su representación de cadena.
Mark Ransom

4
strerrorEvidentemente, nunca ha visto la función.
Kaz

Respuestas:


86

Sí, la vida útil de una variable local está dentro del alcance ( {, }) en el que se crea.

Las variables locales tienen almacenamiento automático o local. Automático porque se destruyen automáticamente una vez que finaliza el alcance dentro del cual se crearon.

Sin embargo, lo que tiene aquí es un literal de cadena, que se asigna en una memoria de solo lectura definida por la implementación. Los literales de cadena son diferentes de las variables locales y permanecen vivos durante toda la vida útil del programa. Tienen una duración estática [Ref 1] de por vida.

¡Una palabra de precaución!

Sin embargo, tenga en cuenta que cualquier intento de modificar el contenido de un literal de cadena es un comportamiento indefinido (UB). Los programas de usuario no pueden modificar el contenido de una cadena literal.
Por lo tanto, siempre se recomienda usar un constwhile declarando una cadena literal.

const char*p = "string"; 

en vez de,

char*p = "string";    

De hecho, en C ++ está en desuso declarar un literal de cadena sin el constaunque no en C. Sin embargo, declarar un literal de cadena con un constle da la ventaja de que los compiladores normalmente le darían una advertencia en caso de que intente modificar el literal de cadena en segundo caso.

Programa de muestra :

#include<string.h> 
int main() 
{ 
    char *str1 = "string Literal"; 
    const char *str2 = "string Literal"; 
    char source[]="Sample string"; 
 
    strcpy(str1,source);    // No warning or error just Uundefined Behavior 
    strcpy(str2,source);    // Compiler issues a warning 
 
    return 0; 
} 

Salida:

cc1: las advertencias se tratan como errores
prog.c: En la función 'main':
prog.c: 9: error: pasar el argumento 1 de 'strcpy' descarta los calificadores del tipo de destino del puntero

Observe que el compilador advierte para el segundo caso, pero no para el primero.


Para responder a la pregunta de un par de usuarios aquí:

¿Cuál es el trato con los literales integrales?

En otras palabras, ¿es válido el siguiente código?

int *foo()
{
    return &(2);
} 

La respuesta es no, este código no es válido. Está mal formado y dará un error de compilación.

Algo como:

prog.c:3: error: lvalue required as unary ‘&’ operand
     

Los literales de cadena son valores l, es decir: puede tomar la dirección de un literal de cadena, pero no puede cambiar su contenido.
Sin embargo, cualquier otro literales ( int, float, char, etc.) son los valores de r (el estándar C utiliza el término el valor de una expresión para estos) y su dirección no puede ser tomada en absoluto.


[Ref 1] Estándar C99 6.4.5 / 5 "Literales de cadena - Semántica":

En la fase de traducción 7, se agrega un byte o código de valor cero a cada secuencia de caracteres multibyte que resulta de una cadena literal o literales. La secuencia de caracteres multibyte se usa luego para inicializar una matriz de duración de almacenamiento estático y la longitud suficiente para contener la secuencia . Para los literales de cadenas de caracteres, los elementos de la matriz tienen el tipo char y se inicializan con los bytes individuales de la secuencia de caracteres multibyte; para literales de cadena ancha, los elementos de la matriz tienen el tipo wchar_t y se inicializan con la secuencia de caracteres anchos ...

No se especifica si estas matrices son distintas siempre que sus elementos tengan los valores apropiados. Si el programa intenta modificar dicha matriz, el comportamiento no está definido .


¿Qué pasa si el usuario está devolviendo algo como esto? char * a = & "abc"; return a; ¿No será esto válido?
Ashwin

@Ashwin: el tipo de cadena literal es char (*)[4]. Esto se debe a que el tipo de "abc" es char[4]y el puntero a una matriz de 4 caracteres se declara como char (*)[4], Entonces, si necesita tomar la dirección, debe hacerlo como char (*a)[4] = &"abc";y Sí, es válido.
Alok Save

@Als "abc" es char[4]. (Debido a '\0')
asaelr

1
Tal vez también sería una buena idea para advertir que char const s[] = "text";no no hacer sun literal de caracteres, y por lo tanto s va a ser destruida al final del alcance, por lo que cualquier punteros que sobreviven a que se cuelgan.
celtschk

1
@celtschk: Me encantaría, pero la Q se trata específicamente de literales de cadena, por lo que me apegaría al tema en cuestión. Sin embargo, para los interesados, mi respuesta aquí, ¿Cuál es la diferencia entre char a [] = "string" y char * p = "cadena"? debería ser bastante útil.
Alok Save

74

Es valido. Los literales de cadena tienen una duración de almacenamiento estática, por lo que el puntero no está colgando.

Para C, eso es obligatorio en la sección 6.4.5, párrafo 6:

En la fase de traducción 7, se agrega un byte o código de valor cero a cada secuencia de caracteres multibyte que resulta de una cadena literal o literales. La secuencia de caracteres multibyte se utiliza para inicializar una matriz de duración de almacenamiento estática y la longitud suficiente para contener la secuencia.

Y para C ++ en la sección 2.14.5, párrafos 8-11:

8 Los literales de cadena ordinarios y los literales de cadena UTF-8 también se conocen como literales de cadena estrecha. Un literal de cadena estrecha tiene el tipo "matriz de n const char", donde n es el tamaño de la cadena como se define a continuación, y tiene una duración de almacenamiento estático (3.7).

9 Un literal de cadena que comienza con u, como u"asdf", es un char16_tliteral de cadena. Un char16_tliteral de cadena tiene el tipo "matriz de n const char16_t", donde n es el tamaño de la cadena como se define a continuación; tiene una duración de almacenamiento estático y se inicializa con los caracteres dados. Un solo c-char puede producir más de un char16_tcarácter en forma de pares sustitutos.

10 Un literal de cadena que comienza con U, como U"asdf", es un char32_tliteral de cadena. Un char32_tliteral de cadena tiene el tipo "matriz de n const char32_t", donde n es el tamaño de la cadena como se define a continuación; tiene una duración de almacenamiento estático y se inicializa con los caracteres dados.

11 Un literal de cadena que comienza con L, como L"asdf", es un literal de cadena ancha. Un literal de cadena ancha tiene el tipo "matriz de n const wchar_t", donde n es el tamaño de la cadena como se define a continuación; tiene una duración de almacenamiento estático y se inicializa con los caracteres dados.


FYI: esta respuesta se combinó de stackoverflow.com/questions/16470959/…
Shog9

14

Los literales de cadena son válidos para todo el programa (y no se asignan ni a la pila), por lo que serán válidos.

Además, los literales de cadena son de solo lectura, por lo que (para un buen estilo) tal vez debería cambiar fooaconst char *foo(int)


¿Qué pasa si el usuario está devolviendo algo como esto? char * a = & "abc"; return a; ¿No será esto válido?
Ashwin

&"abc"no lo es char*. es una dirección de matriz y su tipo es char(*)[4]. Sin embargo, ambos return &"abc";y char *a="abc";return a;son válidos.
asaelr

@asaelr: En realidad, es más que solo por un buen estilo , verifique mi respuesta para obtener más detalles.
Alok Save

@Als Bueno, si escribe todo el programa, puede evitar cambiar la cadena sin escribir const, y será completamente legal, pero seguirá siendo de mal estilo.
asaelr

si es válido para todo el programa, ¿por qué necesitamos malloc?
TomSawyer

7

Sí, es un código válido, consulte el caso 1 a continuación. Puede devolver cadenas C de una función de forma segura al menos de estas formas:

  • const char*a una cadena literal. No se puede modificar y no debe ser liberado por la persona que llama. Rara vez es útil para devolver un valor predeterminado, debido al problema de liberación que se describe a continuación. Podría tener sentido si realmente necesita pasar un puntero de función en algún lugar, por lo que necesita una función que devuelva una cadena.

  • char*oa const char*un búfer de carbón estático. No debe ser liberado por la persona que llama. Puede ser modificado (ya sea por la persona que llama si no es constante, o por la función que lo devuelve), pero una función que devuelve esto no puede (fácilmente) tener múltiples búferes, por lo que no es (fácilmente) seguro para subprocesos, y la persona que llama puede necesitar para copiar el valor devuelto antes de volver a llamar a la función.

  • char*a un búfer asignado con malloc. Se puede modificar, pero normalmente la persona que llama debe liberarlo explícitamente y tiene la sobrecarga de asignación de montón. strdupes de este tipo.

  • const char*oa char*un búfer, que se pasó como argumento a la función (el puntero devuelto no necesita apuntar al primer elemento del búfer de argumentos). Deja la responsabilidad de la gestión del búfer / memoria a quien llama. Muchas funciones de cadena estándar son de este tipo.

Un problema es que mezclarlos en una función puede resultar complicado. La persona que llama necesita saber cómo debe manejar el puntero devuelto, cuánto tiempo es válido y si la persona que llama debe liberarlo, y no hay una forma (agradable) de determinar eso en tiempo de ejecución. Por lo tanto, no puede, por ejemplo, tener una función, que a veces devuelve un puntero a un búfer asignado al montón que la persona que llama necesita free, y a veces un puntero a un valor predeterminado de la cadena literal, que la persona que llama no debe free.


FYI: esta respuesta se combinó de stackoverflow.com/questions/16470959/…
Shog9

6

Buena pregunta. En general, tendría razón, pero su ejemplo es la excepción. El compilador asigna estáticamente memoria global para un literal de cadena. Por lo tanto, la dirección devuelta por su función es válida.

Que esto sea así es una característica bastante conveniente de C, ¿no? Permite que una función devuelva un mensaje precompuesto sin obligar al programador a preocuparse por la memoria en la que se almacena el mensaje.

Véase también la observación correcta de @ asaelr re const.


: ¿Qué pasa si el usuario está devolviendo algo como esto? char * a = & "abc"; return a; ¿No será esto válido?
Ashwin

Derecha. En realidad, uno puede simplemente escribir const char *a = "abc";, omitiendo el &. La razón es que una cadena entre comillas dobles se resuelve en la dirección de su carácter inicial.
thb

3

Las variables locales solo son válidas dentro del alcance que están declaradas, sin embargo, no declara ninguna variable local en esa función.

Es perfectamente válido devolver un puntero a un literal de cadena desde una función, ya que existe un literal de cadena durante toda la ejecución del programa, tal como lo staticharía una variable global o una.

Si le preocupa que lo que está haciendo no sea válido indefinido, debería activar las advertencias del compilador para ver si hay algo que esté haciendo mal.


¿Qué pasa si el usuario está devolviendo algo como esto? char * a = & "abc"; return a; ¿No será esto válido?
Ashwin

@Ashwin: &"abc"no es de tipo char*, sin embargo ambos "abc"y &"abc"son válidos durante toda la ejecución del programa.
AusCBloke

2

strnunca será un puntero colgante, porque apunta a una dirección estática donde residen los literales de cadena.

Será principalmente de solo lectura y global para el programa cuando se cargue.

Incluso si intenta liberar o modificar, arrojará una falla de segmentación en plataformas con protección de memoria .


FYI: esta respuesta se combinó de stackoverflow.com/questions/16470959/…
Shog9

si nunca estará colgando, ¿necesito malloc? ¿No?
TomSawyer

0

Se asigna una variable local en la pila. Una vez finalizada la función, la variable queda fuera de alcance y ya no es accesible en el código. Sin embargo, si tiene un puntero global (o simplemente, que aún no está fuera de alcance) que asignó para apuntar a esa variable, apuntará al lugar en la pila donde estaba esa variable. Podría ser un valor utilizado por otra función o un valor sin sentido.


¿Qué pasa si el usuario está devolviendo algo como esto? char * a = & "abc"; return a; ¿No será esto válido?
Ashwin

0

En el ejemplo anterior mostrado por usted, en realidad está devolviendo los punteros asignados a cualquier función que llame a lo anterior. Entonces no se convertiría en un puntero local. Además, para los punteros que se necesitan devolver, la memoria se asigna en el segmento global.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.