"Lebenszeit" eines String-Literals in C.

83

Wäre der von der folgenden Funktion zurückgegebene Zeiger nicht unzugänglich?

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

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    }
}

Die Lebensdauer einer lokalen Variablen in C / C ++ liegt also praktisch nur innerhalb der Funktion, oder? Was bedeutet, dass char* foo(int)der zurückgegebene Zeiger nach dem Beenden nichts mehr bedeutet, oder?

Ich bin etwas verwirrt über die Lebensdauer einer lokalen Variablen. Was ist eine gute Klarstellung?

user113454
quelle
10
Das einzige "var", das Sie in Ihrer Funktion haben, ist der Parameter int rc. Seine Lebensdauer endet bei jedem der return-s. Die Zeiger, die Sie zurückgeben, beziehen sich auf Zeichenfolgenliterale. String-Literale haben eine statische Speicherdauer: Ihre Lebensdauer ist mindestens so lang wie die des Programms.
Kaz
14
@ PedroAlves Warum nicht? Methoden erlauben Abstraktion; Was ist, wenn die Zeichenfolge in Zukunft aus einer Übersetzungsressource gelesen wird, für V1 (oder V0.5) eines Produkts jedoch keine Unterstützung für die Internationalisierung erforderlich ist?
dlev
1
@PedroAlves "Ihr Code wird sicher funktionieren (und Sie können ihn sehen, wenn Sie versuchen, ihn zu kompilieren)." Das folgt nicht. Viele (die meisten? Im Wesentlichen jeder?) C-Compiler verbrauchen illegalen Code und geben häufig Code aus, der anscheinend funktioniert. Aber versuchen Sie es in einem anderen Compiler (oder sogar einer anderen Version desselben Compilers) und es kann umfallen.
dmckee --- Ex-Moderator Kätzchen
6
@PedroAlves, eine Funktion, die eine einzelne konstante Zeichenfolge zurückgibt, ist möglicherweise nur von begrenztem Nutzen. Wie wäre es jedoch mit einer Funktion, die abhängig von der Eingabe oder dem Objektstatus eine beliebige Anzahl von konstanten Zeichenfolgen zurückgibt? Ein einfaches Beispiel wäre eine Funktion zum Konvertieren einer Aufzählung in ihre Zeichenfolgendarstellung.
Mark Ransom
4
Sie haben die strerrorFunktion offensichtlich noch nie gesehen .
Kaz

Antworten:

86

Ja, die Lebensdauer einer lokalen Variablen liegt innerhalb des Bereichs ( {, }), in dem sie erstellt wird.

Lokale Variablen werden automatisch oder lokal gespeichert. Automatisch, weil sie automatisch zerstört werden, sobald der Bereich, in dem sie erstellt wurden, endet.

Was Sie hier haben, ist jedoch ein Zeichenfolgenliteral, das in einem implementierungsdefinierten Nur-Lese-Speicher zugewiesen wird. String-Literale unterscheiden sich von lokalen Variablen und bleiben während der gesamten Programmlebensdauer erhalten. Sie haben eine statische Lebensdauer [Ref 1] .

Ein Wort der Warnung!

Beachten Sie jedoch, dass jeder Versuch, den Inhalt eines Zeichenfolgenliteral zu ändern, ein undefiniertes Verhalten (UB) ist. Benutzerprogramme dürfen den Inhalt eines String-Literal nicht ändern.
Daher wird immer empfohlen, eine constWeile zu verwenden, um ein Zeichenfolgenliteral zu deklarieren.

const char*p = "string"; 

anstatt,

char*p = "string";    

Tatsächlich ist es in C ++ veraltet, ein Zeichenfolgenliteral ohne das zu deklarieren, constjedoch nicht in C. Wenn Sie jedoch ein Zeichenfolgenliteral mit a deklarieren, haben constSie den Vorteil, dass Compiler normalerweise eine Warnung ausgeben, wenn Sie versuchen, das Zeichenfolgenliteral in zu ändern zweiter Fall.

Beispielprogramm :

#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; 
} 

Ausgabe:

cc1: Warnungen werden als Fehler behandelt
prog.c: In Funktion 'main':
prog.c: 9: Fehler: Übergabe von Argument 1 von 'strcpy' verwirft Qualifizierer vom Zeigerzieltyp

Beachten Sie, dass der Compiler für den zweiten Fall warnt, nicht jedoch für den ersten.


Um die Frage zu beantworten, die einige Benutzer hier gestellt haben:

Was ist mit integralen Literalen los?

Mit anderen Worten, ist der folgende Code gültig?

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

Die Antwort lautet: Nein, dieser Code ist ungültig. Es ist schlecht geformt und gibt einen Compilerfehler aus.

Etwas wie:

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

String-Literale sind l-Werte, dh: Sie können die Adresse eines String-Literals übernehmen, dessen Inhalt jedoch nicht ändern.
Jedoch auch jede andere Literale ( int, float, char, etc.) sind R-Werte (C - Standard verwendet den Begriff der Wert eines Ausdrucks für diese) und deren Adresse gar nicht zu entnehmen.


[Ref 1] C99 Standard 6.4.5 / 5 "String Literals - Semantics":

In der Übersetzungsphase 7 wird an jede Multibyte-Zeichenfolge, die sich aus einem String-Literal oder Literalen ergibt, ein Byte oder ein Code mit dem Wert Null angehängt. Die Multibyte-Zeichenfolge wird dann verwendet, um ein Array mit statischer Speicherdauer und -länge zu initialisieren, das gerade ausreicht, um die Folge aufzunehmen . Bei Zeichenfolgenliteralen haben die Array-Elemente den Typ char und werden mit den einzelnen Bytes der Multibyte-Zeichenfolge initialisiert. Bei Wide-String-Literalen haben die Array-Elemente den Typ wchar_t und werden mit der Folge von Wide-Zeichen initialisiert ...

Es ist nicht spezifiziert, ob diese Arrays unterschiedlich sind, vorausgesetzt, ihre Elemente haben die entsprechenden Werte. Wenn das Programm versucht, ein solches Array zu ändern, ist das Verhalten undefiniert .

Alok Speichern
quelle
Was ist, wenn der Benutzer so etwas zurückgibt? char * a = & "abc"; return a; Wird dies nicht gültig sein?
Ashwin
@Ashwin: Der Typ des String-Literal ist char (*)[4]. Dies liegt daran, dass der Typ "abc" ist char[4]und der Zeiger auf ein Array mit 4 Zeichen als "deklariert " ist. char (*)[4]Wenn Sie also die Adresse übernehmen müssen, müssen Sie dies als char (*a)[4] = &"abc";und "Ja" tun , es ist gültig.
Alok Save
@Als "abc" ist char[4]. (Wegen der '\0')
Asaelr
1
Vielleicht wäre es auch eine gute Idee sein , dass zu warnen , char const s[] = "text";nicht nicht macht seinen Zeichenliteral, und daher s wird am Ende des Bereichs zerstört werden, so dass alle überlebenden Zeiger auf sie baumeln lassen werden.
Celtschk
1
@celtschk: Ich würde es gerne tun, aber das Q handelt speziell von String-Literalen. Also würde ich mich an das vorliegende Thema halten. Für Interessierte ist meine Antwort hier jedoch: Was ist der Unterschied zwischen char a [] = "string" und char * p = "Zeichenfolge"? sollte eher hilfreich sein.
Alok Save
74

Es ist gültig. String-Literale haben eine statische Speicherdauer, sodass der Zeiger nicht baumelt.

Für C ist dies in Abschnitt 6.4.5, Absatz 6 vorgeschrieben:

In der Übersetzungsphase 7 wird an jede Multibyte-Zeichenfolge, die sich aus einem String-Literal oder Literalen ergibt, ein Byte oder ein Code mit dem Wert Null angehängt. Die Multibyte-Zeichenfolge wird dann verwendet , um ein Array mit statischer Speicherdauer und -länge zu initialisieren, das gerade ausreicht, um die Folge aufzunehmen.

Und für C ++ in Abschnitt 2.14.5, Absätze 8-11:

8 Gewöhnliche String-Literale und UTF-8-String-Literale werden auch als schmale String-Literale bezeichnet. Ein schmales String-Literal hat den Typ "Array of n const char", wobei n die Größe des Strings ist, wie unten definiert, und eine statische Speicherdauer (3.7) hat.

9 Ein String-Literal, das mit u beginnt, z. B. u"asdf"ein char16_tString-Literal. Ein char16_tZeichenfolgenliteral hat den Typ "Array von n const char16_t", wobei n die Größe der Zeichenfolge ist, wie unten definiert. Es hat eine statische Speicherdauer und wird mit den angegebenen Zeichen initialisiert. Ein einzelnes c- char16_tZeichen kann mehr als ein Zeichen in Form von Ersatzpaaren erzeugen .

10 Ein String-Literal, das mit U beginnt, z. B. U"asdf"ein char32_tString-Literal. Ein char32_tZeichenfolgenliteral hat den Typ "Array von n const char32_t", wobei n die Größe der Zeichenfolge ist, wie unten definiert. Es hat eine statische Speicherdauer und wird mit den angegebenen Zeichen initialisiert.

11 Ein String-Literal, das mit L beginnt, z. B. L"asdf"ein breites String-Literal. Ein breites String-Literal hat den Typ "Array von n const wchar_t", wobei n die Größe des Strings ist, wie unten definiert. Es hat eine statische Speicherdauer und wird mit den angegebenen Zeichen initialisiert.

Daniel Fischer
quelle
Zu Ihrer
Information
14

String-Literale sind für das gesamte Programm gültig (und werden nicht dem Stack zugewiesen), sodass sie gültig sind.

Auch Stringliterale sind schreibgeschützt, so (für guten Stil) sollten Sie vielleicht ändern foozuconst char *foo(int)

asaelr
quelle
Was ist, wenn der Benutzer so etwas zurückgibt? char * a = & "abc"; return a; Wird dies nicht gültig sein?
Ashwin
&"abc"ist nicht char*. Es ist eine Adresse des Arrays und sein Typ ist char(*)[4]. Jedoch entweder return &"abc";und char *a="abc";return a;sind gültig.
Asaelr
@asaelr: Eigentlich ist es mehr als nur für einen guten Stil , überprüfen Sie meine Antwort für die Details.
Alok Save
@Als Nun, wenn er das gesamte Programm schreibt, kann er vermeiden, den String ohne Schreiben zu ändern const, und es wird völlig legal sein, aber es ist immer noch ein schlechter Stil.
Asaelr
Wenn es für das gesamte Programm gültig ist, warum müssen wir es mallocieren?
TomSawyer
7

Ja, es ist ein gültiger Code, siehe Fall 1 unten. Sie können C-Strings von einer Funktion auf mindestens folgende Weise sicher zurückgeben:

  • const char*zu einem String-Literal. Es kann nicht geändert werden und darf nicht vom Anrufer freigegeben werden. Aufgrund des unten beschriebenen Freigabeproblems ist es für die Rückgabe eines Standardwerts selten nützlich. Es kann sinnvoll sein, wenn Sie tatsächlich irgendwo einen Funktionszeiger übergeben müssen, sodass Sie eine Funktion benötigen, die einen String zurückgibt.

  • char*oder const char*zu einem statischen Zeichenpuffer. Es darf vom Anrufer nicht freigegeben werden. Es kann geändert werden (entweder vom Aufrufer, wenn nicht const, oder von der Funktion, die es zurückgibt), aber eine Funktion, die dies zurückgibt, kann nicht (leicht) mehrere Puffer haben, so dass es nicht (leicht) threadsicher ist und der Aufrufer möglicherweise benötigt um den zurückgegebenen Wert zu kopieren, bevor die Funktion erneut aufgerufen wird.

  • char*zu einem Puffer zugeordnet mit malloc. Es kann geändert werden, muss jedoch normalerweise vom Aufrufer explizit freigegeben werden und hat den Heap-Zuordnungsaufwand. strdupist von diesem Typ.

  • const char*oder char*an einen Puffer, der als Argument an die Funktion übergeben wurde (der zurückgegebene Zeiger muss nicht auf das erste Element des Argumentpuffers zeigen). Die Verantwortung für die Puffer- / Speicherverwaltung liegt beim Anrufer. Viele Standardzeichenfolgenfunktionen sind von diesem Typ.

Ein Problem ist, dass das Mischen dieser in einer Funktion kompliziert werden kann. Der Aufrufer muss wissen, wie er mit dem zurückgegebenen Zeiger umgehen soll, wie lange er gültig ist und ob der Anrufer ihn freigeben soll, und es gibt keine (nette) Möglichkeit, dies zur Laufzeit zu bestimmen. So können Sie beispielsweise keine Funktion haben, die manchmal einen Zeiger auf einen vom Heap zugewiesenen Puffer zurückgibt, den der Aufrufer benötigt free, und manchmal einen Zeiger auf einen Standardwert aus dem Zeichenfolgenliteral, den der Aufrufer nicht muss free.

Hyde
quelle
Zu Ihrer
Information
6

Gute Frage. Im Allgemeinen hätten Sie Recht, aber Ihr Beispiel ist die Ausnahme. Der Compiler reserviert statisch globalen Speicher für ein Zeichenfolgenliteral. Daher ist die von Ihrer Funktion zurückgegebene Adresse gültig.

Dass dies so ist, ist ein ziemlich praktisches Merkmal von C, nicht wahr? Es ermöglicht einer Funktion, eine vorkomponierte Nachricht zurückzugeben, ohne den Programmierer zu zwingen, sich Gedanken über den Speicher zu machen, in dem die Nachricht gespeichert ist.

Siehe auch @ asaelrs korrekte Beobachtung bezüglich const.

thb
quelle
: Was ist, wenn der Benutzer so etwas zurückgibt? char * a = & "abc"; return a; Wird dies nicht gültig sein?
Ashwin
Richtig. Eigentlich kann man einfach schreiben const char *a = "abc";und das weglassen &. Der Grund dafür ist, dass eine Zeichenfolge in doppelten Anführungszeichen in die Adresse ihres ursprünglichen Zeichens aufgelöst wird.
thb
3

Lokale Variablen sind nur in dem Bereich gültig, in dem sie deklariert sind. Sie deklarieren jedoch keine lokalen Variablen in dieser Funktion.

Es ist absolut gültig, einen Zeiger von einer Funktion auf ein Zeichenfolgenliteral zurückzugeben, da während der gesamten Ausführung des Programms ein Zeichenfolgenliteral vorhanden ist, genau wie eine staticoder eine globale Variable.

Wenn Sie sich Sorgen darüber machen, was Sie tun, könnte dies undefiniert ungültig sein, sollten Sie Ihre Compiler-Warnungen aufdrehen, um festzustellen, ob tatsächlich etwas falsch ist.

AusCBloke
quelle
Was ist, wenn der Benutzer so etwas zurückgibt? char * a = & "abc"; return a; Wird dies nicht gültig sein?
Ashwin
@Ashwin: &"abc"ist nicht vom Typ char*, jedoch beides "abc"und &"abc"gilt während der gesamten Programmausführung.
AusCBloke
2

strwird niemals ein baumelnder Zeiger sein, da er auf eine statische Adresse verweist, an der sich Zeichenfolgenliterale befinden.

Es ist meistens schreibgeschützt und global für das Programm, wenn es geladen wird.

Selbst wenn Sie versuchen, freizugeben oder zu ändern, wird auf Plattformen mit Speicherschutz ein Segmentierungsfehler ausgelöst .

qwr
quelle
Zu Ihrer
Information
Wenn es niemals baumeln wird, muss ich es mallocieren? Nein?
TomSawyer
0

Auf dem Stapel wird eine lokale Variable zugewiesen. Nach Beendigung der Funktion verlässt die Variable den Gültigkeitsbereich und ist im Code nicht mehr verfügbar. Wenn Sie jedoch einen globalen (oder einfach - noch nicht außerhalb des Gültigkeitsbereichs) Zeiger haben, den Sie zugewiesen haben, um auf diese Variable zu zeigen, zeigt er auf die Stelle im Stapel, an der sich diese Variable befand. Dies kann ein Wert sein, der von einer anderen Funktion verwendet wird, oder ein bedeutungsloser Wert.

Imp
quelle
Was ist, wenn der Benutzer so etwas zurückgibt? char * a = & "abc"; return a; Wird dies nicht gültig sein?
Ashwin
0

In dem von Ihnen gezeigten obigen Beispiel geben Sie die zugewiesenen Zeiger tatsächlich an die Funktion zurück, die die oben genannten Funktionen aufruft. Es würde also kein lokaler Zeiger werden. Darüber hinaus wird für die Zeiger, die zurückgegeben werden müssen, Speicher im globalen Segment zugewiesen.

VIHARRI PLV
quelle