Veraltete Konvertierung von String-Konstante zu 'char *'

16

Was bedeutet dieser Fehler? Ich kann es in keiner Weise lösen.

Warnung: Veraltete Konvertierung von String-Konstante zu 'char *' [-Wwrite-strings]

Federico Corazza
quelle
Diese Frage sollte auf StackOverflow sein, nicht auf Arduino :)
Vijay Chavda

Antworten:

26

Wie es meine Gewohnheit ist, werde ich ein paar technische Hintergrundinformationen zum Warum und Woher dieses Fehlers geben.

Ich werde vier verschiedene Arten der Initialisierung von C-Strings untersuchen und die Unterschiede zwischen ihnen herausfinden. Dies sind die vier in Frage kommenden Möglichkeiten:

char *text = "This is some text";
char text[] = "This is some text";
const char *text = "This is some text";
const char text[] = "This is some text";

Aus diesem Grund möchte ich den dritten Buchstaben "i" in "o" ändern, um daraus "Thos is some text" zu machen. Dies könnte in allen Fällen (Sie würden denken) erreicht werden durch:

text[2] = 'o';

Schauen wir uns nun an, was die einzelnen Deklarationsarten der Zeichenfolge bewirken und wie sich diese text[2] = 'o';Anweisung auf die Dinge auswirken würde.

Zunächst wird die am häufigsten gesehen Art und Weise: char *text = "This is some text";. Was bedeutet das wörtlich? Nun, in C bedeutet es wörtlich "Erzeuge eine Variable, die aufgerufen textwird und ein Lese- / Schreibzeiger auf dieses Zeichenkettenliteral ist, das im Nur-Lese- (Code-) Raum enthalten ist". Wenn Sie die Option -Wwrite-stringsaktiviert haben , wird eine Warnung angezeigt (siehe Frage oben).

Grundsätzlich bedeutet dies "Warnung: Sie haben versucht, eine Variable mit Lese- / Schreibzugriff auf einen Bereich zu setzen, in den Sie nicht schreiben können". Wenn Sie versuchen, das dritte Zeichen auf "o" zu setzen, versuchen Sie tatsächlich, in einen schreibgeschützten Bereich zu schreiben, und die Dinge werden nicht schön. Auf einem herkömmlichen PC mit Linux führt dies zu:

Segmentierungsfehler

Nun ist die zweite: char text[] = "This is some text";. In C bedeutet dies wörtlich: "Erstellen Sie ein Array vom Typ" char "und initialisieren Sie es mit den Daten" Dies ist etwas Text \ 0 ". Die Größe des Arrays ist groß genug, um die Daten zu speichern." Das reserviert also tatsächlich RAM und kopiert zur Laufzeit den Wert "Dies ist etwas Text \ 0" hinein. Keine Warnungen, keine Fehler, vollkommen gültig. Und die richtige Vorgehensweise, wenn Sie die Daten bearbeiten möchten . Versuchen wir den Befehl auszuführen text[2] = 'o':

Das ist ein Text

Es hat perfekt funktioniert. Gut.

Nun ist die dritte Möglichkeit: const char *text = "This is some text";. Wieder die wörtliche Bedeutung: "Erstellen Sie eine Variable mit dem Namen" text ", die ein schreibgeschützter Zeiger auf diese Daten im Nur-Lese-Speicher ist." Beachten Sie, dass sowohl der Zeiger als auch die Daten jetzt schreibgeschützt sind. Keine Fehler, keine Warnungen. Was passiert, wenn wir versuchen, unseren Testbefehl auszuführen? Das können wir nicht. Der Compiler ist jetzt intelligent und weiß, dass wir versuchen, etwas Schlechtes zu tun:

Fehler: Zuweisung des schreibgeschützten Speicherorts '* (Text + 2u)'

Es wird nicht einmal kompiliert. Der Versuch, in den Nur-Lese-Speicher zu schreiben, ist jetzt geschützt, da wir dem Compiler mitgeteilt haben, dass unser Zeiger auf den Nur-Lese-Speicher gerichtet ist. Natürlich ist es nicht hat Speicher zu schreibgeschützt zu zeigen, aber wenn Sie darauf zeigen Speicher - Lese-Schreib (RAM) , dass der Speicher noch nicht geschrieben , um durch den Compiler geschützt werden.

Schließlich die letzte Form: const char text[] = "This is some text";. Auch hier []ordnet es wie zuvor ein Array im RAM zu und kopiert die Daten hinein. Dies ist jedoch jetzt ein schreibgeschütztes Array. Sie können nicht darauf schreiben, da der Zeiger darauf als markiert ist const. Der Versuch, darauf zu schreiben, führt zu:

Fehler: Zuweisung des schreibgeschützten Speicherorts '* (Text + 2u)'

Also, eine kurze Zusammenfassung, wo wir sind:

Dieses Formular ist vollständig ungültig und sollte unter allen Umständen vermieden werden. Es öffnet die Tür für alle Arten von schlechten Dingen:

char *text = "This is some text";

Dieses Formular ist das richtige, wenn Sie die Daten bearbeitbar machen möchten:

char text[] = "This is some text";

Dieses Formular ist das richtige, wenn Sie Zeichenfolgen möchten, die nicht bearbeitet werden können:

const char *text = "This is some text";

Diese Form scheint RAM verschwenderisch zu sein, hat aber ihre Verwendung. Am besten vorerst vergessen.

const char text[] = "This is some text";
Majenko
quelle
6
Es ist erwähnenswert, dass auf dem Arduinos (die AVR-basiert ist zumindest), Stringliterale in RAM lebt, wenn Sie sie mit einem Makro wie erklären PROGMEM, PSTR()oder F(). So const char text[]verwendet nicht mehr RAM als const char *text.
Edgar Bonet
Teensyduino und viele andere neuere Arduino-kompatible Geräte platzieren automatisch String-Literale im Code-Bereich. Es lohnt sich also zu prüfen, ob F () auf Ihrem Board benötigt wird oder nicht.
Craig.Feied
@ Craig.Feied Im Allgemeinen sollte F () unabhängig davon verwendet werden. Diejenigen, die es nicht "brauchen", neigen dazu, es als einfaches (const char *)(...)Casting zu definieren . Kein wirklicher Effekt, wenn das Board es nicht benötigt, aber eine große Ersparnis, wenn Sie dann Ihren Code auf ein Board portieren, das dies tut.
Majenko
5

Um die ausgezeichnete Antwort von Makenko zu erläutern, gibt es einen guten Grund, warum der Compiler Sie davor warnt. Machen wir eine Testskizze:

char *foo = "This is some text";
char *bar = "This is some text";

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo [2] = 'o';     // change foo only
  Serial.println (foo);
  Serial.println (bar);
  }  // end of setup

void loop ()
  {
  }  // end of loop

Wir haben hier zwei Variablen, foo und bar. Ich ändere eine davon in setup (), sehe aber, was das Ergebnis ist:

Thos is some text
Thos is some text

Sie haben sich beide umgezogen!

In der Tat, wenn wir uns die Warnungen ansehen, sehen wir:

sketch_jul14b.ino:1: warning: deprecated conversion from string constant to char*’
sketch_jul14b.ino:2: warning: deprecated conversion from string constant to char*’

Der Compiler weiß, dass dies zweifelhaft ist, und es ist richtig! Der Grund dafür ist, dass der Compiler (vernünftigerweise) erwartet, dass sich die String-Konstanten nicht ändern (da es sich um Konstanten handelt). Wenn Sie also "This is some text"in Ihrem Code mehrmals auf die Zeichenfolgenkonstante verweisen, ist es zulässig , allen denselben Speicher zuzuweisen . Wenn Sie jetzt eine ändern, ändern Sie alle!

Nick Gammon
quelle
Ach du meine Güte! Wer hätte das gewusst? Gilt das noch für die neuesten ArduinoIDE-Compiler? Ich habe es gerade auf einem ESP32 ausprobiert und es verursacht wiederholte GuruMeditation- Fehler.
not2qubit
@ not2qubit Ich habe gerade auf Arduino 1.8.9 getestet und es stimmt dort.
Nick Gammon
Die Warnungen haben einen Grund. Diesmal habe ich Folgendes erhalten : Warnung: ISO C ++ verbietet das Konvertieren einer Zeichenfolgekonstante in 'char ' [-Wwrite-strings] char bar = "Dies ist ein Text"; - VERBOTEN ist ein starkes Wort. Da dies verboten ist, kann der Compiler frei herumspielen und dieselbe Zeichenfolge für zwei Variablen verwenden. Mach keine verbotenen Dinge! (Lesen und beseitigen Sie auch die Warnungen). :)
Nick Gammon
Für den Fall, dass Sie auf solch beschissenen Code stoßen und den Tag überleben möchten. Würde eine anfängliche Deklaration *foound *barVerwendung unterschiedlicher String- "Konstanten" dies verhindern? Inwiefern unterscheidet sich dies auch davon, überhaupt keine Zeichenfolgen zu setzen, wie zum Beispiel char *foo;:?
not2qubit
1
Verschiedene Konstanten könnten helfen, aber ich persönlich würde nichts dort ablegen und später Daten dort auf die übliche Weise ablegen (z. B. mit new, strcpyund delete).
Nick Gammon
4

Beenden Sie entweder den Versuch, eine Zeichenfolgenkonstante an die Stelle zu übergeben, an der eine Funktion a annimmt char*, oder ändern Sie die Funktion so, dass sie a annimmt const char*.

Zeichenfolgen wie "Zufallszeichenfolgen" sind Konstanten.

Ignacio Vazquez-Abrams
quelle
Ist ein Text wie "Zufallszeichen" ein konstantes Zeichen?
Federico Corazza
1
String-Literale sind String-Konstanten.
Ignacio Vazquez-Abrams
3

Beispiel:

void foo (char * s)
  {
  Serial.println (s);
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo ("bar");
  }  // end of setup

void loop ()
  {
  }  // end of loop

Warnung:

sketch_jul14b.ino: In function ‘void setup()’:
sketch_jul14b.ino:10: warning: deprecated conversion from string constant to ‘char*’

Die Funktion fooerwartet ein Zeichen * (das sie daher ändern kann), aber Sie übergeben ein Zeichenfolgenliteral, das nicht geändert werden sollte.

Der Compiler warnt Sie davor. Wenn es veraltet ist, wird es möglicherweise in einer zukünftigen Compilerversion zu einem Fehler.


Lösung: Lassen Sie foo einen const char * nehmen:

void foo (const char * s)
  {
  Serial.println (s);
  }

Ich verstehe es nicht. Meinst du, kann nicht geändert werden?

In älteren Versionen von C (und C ++) können Sie Code wie in meinem obigen Beispiel schreiben. Sie könnten eine Funktion (wie foo) erstellen, die etwas druckt, das Sie übergeben haben, und dann eine wörtliche Zeichenfolge (z. B. foo ("Hi there!");) übergeben.

Eine Funktion, die char *als Argument nimmt, darf jedoch ihr Argument ändern (dh Hi there!in diesem Fall ändern ).

Sie könnten zum Beispiel geschrieben haben:

void foo (char * s)
  {
  Serial.println (s);
  strcpy (s, "Goodbye");
  }

Durch die Übergabe eines Literal haben Sie dieses Literal jetzt möglicherweise so geändert, dass "Hi there!" ist jetzt "Auf Wiedersehen", was nicht gut ist. Wenn Sie eine längere Zeichenfolge kopieren, werden möglicherweise andere Variablen überschrieben. Oder bei einigen Implementierungen wird eine Zugriffsverletzung angezeigt, weil "Hi there!" Möglicherweise wurde es in den schreibgeschützten Arbeitsspeicher (RAM) gestellt.

Daher wird diese Verwendung von den Compilern nach und nach abgelehnt , sodass Funktionen, an die Sie ein Literal übergeben, dieses Argument als deklarieren müssen const.

Nick Gammon
quelle
Ist es ein Problem, wenn ich keinen Zeiger verwende?
Federico Corazza
Was für ein Problem? Bei dieser speziellen Warnung geht es um die Konvertierung einer Zeichenfolgekonstante in einen char * -Zeiger. Können Sie näher darauf eingehen?
Nick Gammon
@Nick: Was meinst du mit "(..) Sie übergeben ein String-Literal, das nicht geändert werden sollte". Ich verstehe es nicht. Meinst du can notmodifiziert werden?
Mads Skjern
Ich habe meine Antwort geändert. Majenko ging in seiner Antwort auf die meisten dieser Punkte ein.
Nick Gammon
1

Ich habe diesen Kompilierungsfehler:

TimeSerial.ino:68:29: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
   if(Serial.find(TIME_HEADER)) {

                         ^

Bitte ersetzen Sie diese Zeile:
#define TIME_HEADER "T" // Header tag for serial time sync message

mit dieser Zeile:
#define TIME_HEADER 'T' // Header tag for serial time sync message

und die zusammenstellung geht gut.

Gin
quelle
3
Diese Änderung ändert die Definition von einer Zeichenfolge mit einem Zeichen "T" in ein einzelnes Zeichen mit dem Wert des ASCII-Codes für das Großbuchstaben T.
dlu