"Durata di vita" di una stringa letterale in C


85

Il puntatore restituito dalla seguente funzione non sarebbe inaccessibile?

Quindi la durata di una variabile locale in C / C ++ è praticamente solo all'interno della funzione, giusto? Il che significa che, una volta char* foo(int)terminato, il puntatore restituito non significa più niente, giusto?

Sono un po 'confuso sulla durata di una variabile locale. Qual è un buon chiarimento?


10
L'unico "var" che hai nella tua funzione è il parametro int rc. La sua vita finisce a ciascuno dei return-s. I puntatori che stai restituendo sono a stringhe letterali. I valori letterali stringa hanno una durata di archiviazione statica: la loro durata è almeno pari a quella del programma.
Kaz

14
@PedroAlves Perché no? I metodi consentono l'astrazione; e se in futuro la stringa verrà letta da una risorsa di traduzione, ma il supporto per l'internazionalizzazione non è necessario per V1 (o V0.5) di un prodotto?
dlev

1
@PedroAlves "Il tuo codice funzionerà sicuramente (e puoi vederlo se provi a compilare)," Questo non segue. Molti compilatori c (quasi tutti? Essenzialmente ogni?) Consumano codice illegale e spesso emettono codice che sembra funzionare. Ma provalo in un altro compilatore (o anche in una versione diversa dello stesso compilatore) e potrebbe cadere.
dmckee --- gattino ex moderatore

6
@PedroAlves, una funzione che restituisce una singola stringa costante potrebbe essere di uso limitato, ma che ne dici di una che restituisce una qualsiasi di un numero di stringhe costanti a seconda dell'input o dello stato dell'oggetto? Un semplice esempio potrebbe essere una funzione per convertire un'enumerazione nella sua rappresentazione di stringa.
Mark Ransom

4
Non hai mai visto la strerrorfunzione, evidentemente.
Kaz

Risposte:


86

Sì, la durata di una variabile locale rientra nell'ambito ( {, }) in cui è stata creata.

Le variabili locali hanno una memorizzazione automatica o locale. Automatici perché vengono automaticamente distrutti una volta terminato l'ambito entro il quale sono stati creati.

Tuttavia, quello che hai qui è una stringa letterale, che viene allocata in una memoria di sola lettura definita dall'implementazione. I valori letterali stringa sono diversi dalle variabili locali e rimangono attivi per tutta la durata del programma. Hanno durata statica [Rif 1] .

Una parola di cautela!

Tuttavia, si noti che qualsiasi tentativo di modificare il contenuto di una stringa letterale è un comportamento non definito (UB). Ai programmi utente non è consentito modificare il contenuto di una stringa letterale.
Pertanto, è sempre consigliabile utilizzare un constwhile che dichiara una stringa letterale.

invece di,

Infatti, in C ++ è deprecato dichiarare una stringa letterale senza il constpensiero non in C. Tuttavia, dichiarare una stringa letterale con a constti dà il vantaggio che i compilatori di solito ti darebbero un avviso nel caso in cui tenti di modificare la stringa letterale in secondo caso.

Programma di esempio :

Produzione:

cc1: gli avvisi vengono trattati come errori
prog.c: Nella funzione 'main':
prog.c: 9: errore: il passaggio dell'argomento 1 di 'strcpy' elimina i qualificatori dal tipo di destinazione del puntatore

Si noti che il compilatore avverte per il secondo caso, ma non per il primo.


Per rispondere alla domanda posta da un paio di utenti qui:

Qual è il problema con i letterali integrali?

In altre parole, il seguente codice è valido?

La risposta è no, questo codice non è valido. È mal formato e darà un errore del compilatore.

Qualcosa di simile a:

I letterali stringa sono valori l, cioè: Puoi prendere l'indirizzo di una stringa letterale, ma non puoi cambiarne il contenuto.
Tuttavia, qualsiasi altro letterali ( int, float, char, etc.) sono R-valori (standard C usa il termine valore di un'espressione per questi) e indirizzo non può essere presa affatto.


[Rif 1] C99 standard 6.4.5 / 5 "String Literals - Semantics":

Nella fase di traduzione 7, un byte o un codice di valore zero viene aggiunto a ciascuna sequenza di caratteri multibyte risultante da una stringa letterale o letterale. La sequenza di caratteri multibyte viene quindi utilizzata per inizializzare un array di durata e lunghezza della memorizzazione statica appena sufficienti a contenere la sequenza . Per le stringhe di caratteri letterali, gli elementi dell'array hanno il tipo char e vengono inizializzati con i singoli byte della sequenza di caratteri multibyte; per i valori letterali di stringa ampia, gli elementi dell'array hanno il tipo wchar_t e vengono inizializzati con la sequenza di caratteri larghi ...

Non è specificato se questi array siano distinti a condizione che i loro elementi abbiano i valori appropriati. Se il programma tenta di modificare un tale array, il comportamento è indefinito .


E se l'utente restituisse qualcosa di simile. char * a = & "abc"; return a; Non sarà valido?
Ashwin

@ Ashwin: il tipo di stringa letterale è char (*)[4]. Questo perché il tipo di "abc" è char[4]e il puntatore a un array di 4 caratteri è dichiarato come char (*)[4], quindi se devi prenderne l'indirizzo, devi farlo come char (*a)[4] = &"abc";e Sì, è valido.
Alok Save

@Als "abc" è char[4]. (A causa del '\0')
asaelr

1
Forse sarebbe anche una buona idea per avvertire che char const s[] = "text";non non fare sun letterale carattere, e quindi s sarà essere distrutta alla fine del campo di applicazione, in modo da tutti i puntatori che sopravvivono ad esso saranno penzolare.
celtschk

1
@celtschk: mi piacerebbe, ma la Q riguarda specificamente le stringhe letterali Quindi mi atterrei all'argomento in questione. Tuttavia, per quelli interessati la mia risposta qui, Qual è la differenza tra char a [] = "string" e char * p = "stringa"? dovrebbe essere piuttosto utile.
Alok Save

74

È valido. I valori letterali stringa hanno una durata di archiviazione statica, quindi il puntatore non penzola.

Per C, ciò è obbligatorio nella sezione 6.4.5, paragrafo 6:

Nella fase di traduzione 7, un byte o un codice di valore zero viene aggiunto a ciascuna sequenza di caratteri multibyte risultante da una stringa letterale o letterale. La sequenza di caratteri multibyte viene quindi utilizzata per inizializzare un array di durata e lunghezza di memorizzazione statica appena sufficienti a contenere la sequenza.

E per C ++ nella sezione 2.14.5, paragrafi 8-11:

8 I valori letterali stringa ordinari e i valori letterali stringa UTF-8 sono indicati anche come valori letterali stringa stretti. Un valore letterale stringa stretto ha il tipo "array of n const char", dove n è la dimensione della stringa come definito di seguito e ha una durata di archiviazione statica (3.7).

9 Una stringa letterale che inizia con u, ad esempio u"asdf", è una char16_tstringa letterale. Un valore char16_tletterale stringa ha il tipo "array of n const char16_t", dove n è la dimensione della stringa come definito di seguito; ha una durata di memorizzazione statica e viene inizializzato con i caratteri specificati. Un singolo carattere c può produrre più di un char16_tcarattere sotto forma di coppie surrogate.

10 Una stringa letterale che inizia con U, ad esempio U"asdf", è una char32_tstringa letterale. Un valore char32_tletterale stringa ha il tipo "array of n const char32_t", dove n è la dimensione della stringa come definito di seguito; ha una durata di memorizzazione statica e viene inizializzato con i caratteri specificati.

11 Una stringa letterale che inizia con L, ad esempio L"asdf", è una stringa ampia. Una stringa letterale ampia ha il tipo "array of n const wchar_t", dove n è la dimensione della stringa come definito di seguito; ha una durata di memorizzazione statica e viene inizializzato con i caratteri specificati.


FYI: questa risposta è stata incorporata da stackoverflow.com/questions/16470959/...
Shog9

14

I valori letterali stringa sono validi per l'intero programma (e non sono allocati né per lo stack), quindi saranno validi.

Inoltre, le stringhe letterali sono di sola lettura, quindi (per un buon stile) forse dovresti passare fooaconst char *foo(int)


E se l'utente restituisse qualcosa di simile. char * a = & "abc"; return a; Non sarà valido?
Ashwin

&"abc"non lo è char*. è un indirizzo di matrice e il suo tipo è char(*)[4]. Tuttavia, entrambi return &"abc";e char *a="abc";return a;sono validi.
asaelr

@asaelr: In realtà, è più che solo per il bene di un buon stile , controlla la mia risposta per i dettagli.
Alok Save

@Als Beh, se scrive l'intero programma, può evitare di cambiare la stringa senza scrivere const, e sarà completamente legale, ma ha comunque un cattivo stile.
asaelr

se è valido per l'intero programma, perché dobbiamo mallocarlo?
TomSawyer

7

Sì, è un codice valido, vedere il caso 1 di seguito. Puoi restituire in modo sicuro stringhe C da una funzione almeno in questi modi:

  • const char*a una stringa letterale. Non può essere modificato e non deve essere liberato dal chiamante. Raramente è utile allo scopo di restituire un valore predefinito, a causa del problema di liberazione descritto di seguito. Potrebbe avere senso se hai effettivamente bisogno di passare un puntatore a funzione da qualche parte, quindi hai bisogno di una funzione che restituisca una stringa.

  • char*o const char*in un buffer di caratteri statici. Non deve essere liberato dal chiamante. Può essere modificato (sia dal chiamante se non const, o dalla funzione che lo restituisce), ma una funzione che lo restituisce non può (facilmente) avere più buffer, quindi non è (facilmente) threadsafe, e il chiamante potrebbe aver bisogno per copiare il valore restituito prima di chiamare nuovamente la funzione.

  • char*in un buffer allocato con malloc. Può essere modificato, ma in genere deve essere liberato in modo esplicito dal chiamante e presenta l'overhead di allocazione dell'heap. strdupè di questo tipo.

  • const char*o char*a un buffer, che è stato passato come argomento alla funzione (il puntatore restituito non deve necessariamente puntare al primo elemento dell'argomento buffer). Lascia la responsabilità della gestione del buffer / memoria al chiamante. Molte funzioni di stringa standard sono di questo tipo.

Un problema è che mescolarli in una funzione può diventare complicato. Il chiamante ha bisogno di sapere come dovrebbe gestire il puntatore restituito, per quanto tempo è valido e se il chiamante deve liberarlo, e non c'è un modo (carino) per determinarlo in fase di esecuzione. Quindi non è possibile, ad esempio, avere una funzione, che a volte restituisce un puntatore a un buffer allocato nell'heap di cui il chiamante ha bisogno free, e talvolta un puntatore a un valore predefinito da una stringa letterale, che il chiamante non deve free.


FYI: questa risposta è stata incorporata da stackoverflow.com/questions/16470959/...
Shog9

6

Buona domanda. In generale, avresti ragione, ma il tuo esempio è l'eccezione. Il compilatore alloca staticamente la memoria globale per una stringa letterale. Pertanto, l'indirizzo restituito dalla funzione è valido.

Che sia così è una caratteristica piuttosto conveniente di C, non è vero? Consente a una funzione di restituire un messaggio precomposto senza costringere il programmatore a preoccuparsi della memoria in cui è memorizzato il messaggio.

Vedi anche l'osservazione corretta di @ asaelr re const.


: E se l'utente restituisse qualcosa di simile. char * a = & "abc"; return a; Non sarà valido?
Ashwin

Destra. In realtà, si può semplicemente scrivere const char *a = "abc";, omettendo il file &. Il motivo è che una stringa tra virgolette doppie si risolve nell'indirizzo del suo carattere iniziale.
gio

3

Le variabili locali sono valide solo all'interno dell'ambito in cui sono dichiarate, tuttavia non dichiari alcuna variabile locale in quella funzione.

È perfettamente valido restituire un puntatore a una stringa letterale da una funzione, poiché una stringa letterale esiste durante l'intera esecuzione del programma, proprio come staticfarebbe una o una variabile globale.

Se ti preoccupi che ciò che stai facendo potrebbe non essere definito non valido, dovresti attivare gli avvisi del compilatore per vedere se c'è effettivamente qualcosa che stai facendo di sbagliato.


E se l'utente restituisse qualcosa di simile. char * a = & "abc"; return a; Non sarà valido?
Ashwin

@ Ashwin: &"abc"non è di tipo char*, tuttavia entrambi "abc"e &"abc"sono validi durante l'intera esecuzione del programma.
AusCBloke

2

strnon sarà mai un puntatore penzolante, perché punta a un indirizzo statico in cui risiedono le stringhe letterali.

Sarà per lo più di sola lettura e globale per il programma quando verrà caricato.

Anche se si tenta di liberare o modificare, verrà generato un errore di segmentazione su piattaforme con protezione della memoria .


FYI: questa risposta è stata incorporata da stackoverflow.com/questions/16470959/...
Shog9

se non sarà mai penzoloni, devo mallocarlo? No?
TomSawyer

0

Una variabile locale viene allocata nello stack. Al termine della funzione, la variabile esce dall'ambito e non è più accessibile nel codice. Tuttavia, se hai un puntatore globale (o semplicemente - non ancora fuori ambito) che hai assegnato per puntare a quella variabile, punterà al punto nello stack in cui si trovava quella variabile. Potrebbe essere un valore utilizzato da un'altra funzione o un valore privo di significato.


E se l'utente restituisse qualcosa di simile. char * a = & "abc"; return a; Non sarà valido?
Ashwin

0

Nell'esempio sopra mostrato da te, stai effettivamente restituendo i puntatori allocati a qualsiasi funzione che richiami quanto sopra. Quindi non diventerebbe un puntatore locale. Inoltre, per i puntatori che devono essere restituiti, la memoria viene allocata nel segmento globale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.