przestarzała konwersja stałej stałej na „char *”

16

Co oznacza ten błąd? Nie mogę tego rozwiązać w żaden sposób.

ostrzeżenie: przestarzała konwersja stałej stałej na „char *” [-Wwrite-strings]

Federico Corazza
źródło
To pytanie powinno dotyczyć StackOverflow, a nie Arduino :)
Vijay Chavda

Odpowiedzi:

26

Jak zwykle, przekażę trochę podstawowych informacji technicznych na temat przyczyn i przyczyn tego błędu.

Sprawdzę cztery różne sposoby inicjowania ciągów C i zobaczę, jakie są między nimi różnice. Oto cztery omawiane sposoby:

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";

Teraz chcę zmienić trzecią literę „i” na „o”, aby „Thos to jakiś tekst”. Można to we wszystkich przypadkach (można by pomyśleć) osiągnąć poprzez:

text[2] = 'o';

Teraz spójrzmy na to, co robi każdy sposób deklarowania łańcucha i jak to text[2] = 'o';zdanie wpłynie na różne rzeczy.

Najpierw najczęściej postrzegane sposób: char *text = "This is some text";. Co to dosłownie znaczy? Cóż, w C oznacza dosłownie „Utwórz zmienną o nazwie, textktóra jest wskaźnikiem odczytu i zapisu do tego literału łańcuchowego, który jest przechowywany w przestrzeni tylko do odczytu (kodu).”. Jeśli masz włączoną opcję -Wwrite-strings, otrzymasz ostrzeżenie, jak pokazano w powyższym pytaniu.

Zasadniczo oznacza to „Ostrzeżenie: Próbowano utworzyć zmienną, która jest odczytem i zapisem, wskazuje na obszar, do którego nie można pisać”. Jeśli spróbujesz, a następnie ustawisz trzeci znak na „o”, w rzeczywistości próbowałbyś napisać w obszarze tylko do odczytu i nic nie będzie przyjemne. Na tradycyjnym komputerze z systemem Linux, który powoduje:

Błąd segmentacji

Teraz druga: char text[] = "This is some text";. Dosłownie w języku C oznacza to „Utwórz tablicę typu„ char ”i zainicjuj ją danymi„ To jest tekst \ 0 ”. Rozmiar tablicy będzie wystarczająco duży, aby przechowywać dane”. Tak więc faktycznie przydziela pamięć RAM i kopiuje do niej wartość „To jest tekst \ 0” w czasie wykonywania. Bez ostrzeżeń, bez błędów, całkowicie poprawne. I właściwy sposób, aby to zrobić, jeśli chcesz móc edytować dane . Spróbujmy uruchomić polecenie text[2] = 'o':

To jest jakiś tekst

Działa idealnie. Dobry.

Teraz trzeci sposób: const char *text = "This is some text";. Znów dosłowne znaczenie: „Utwórz zmienną o nazwie„ tekst ”, która jest wskaźnikiem tylko do odczytu dla tych danych w pamięci tylko do odczytu.”. Pamiętaj, że zarówno wskaźnik, jak i dane są teraz tylko do odczytu. Bez błędów, bez ostrzeżeń. Co się stanie, jeśli spróbujemy uruchomić nasze polecenie testowe? Nie możemy. Kompilator jest teraz inteligentny i wie, że próbujemy zrobić coś złego:

błąd: przypisanie lokalizacji tylko do odczytu „* (tekst + 2u)”

Nawet się nie skompiluje. Próba zapisu do pamięci tylko do odczytu jest teraz chroniona, ponieważ powiedzieliśmy kompilatorowi, że naszym wskaźnikiem jest pamięć tylko do odczytu. Oczywiście nie musi to wskazywać na pamięć tylko do odczytu, ale jeśli wskażesz na pamięć do odczytu i zapisu (RAM), pamięć ta będzie nadal chroniona przed zapisaniem przez kompilator.

Wreszcie ostatnia forma: const char text[] = "This is some text";. Ponownie, podobnie jak poprzednio [], przydziela tablicę w pamięci RAM i kopiuje do niej dane. Jednak teraz jest to tablica tylko do odczytu. Nie możesz do niego pisać, ponieważ wskaźnik do niego jest oznaczony jako const. Próba napisania do niego powoduje:

błąd: przypisanie lokalizacji tylko do odczytu „* (tekst + 2u)”

Krótkie podsumowanie tego, gdzie jesteśmy:

Ten formularz jest całkowicie nieprawidłowy i należy go unikać za wszelką cenę. Otwiera drzwi do wszelkiego rodzaju złych rzeczy:

char *text = "This is some text";

Ten formularz jest odpowiedni, jeśli chcesz, aby dane były edytowalne:

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

Ten formularz jest odpowiedni, jeśli chcesz ciągów, które nie będą edytowane:

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

Ta forma wydaje się marnować pamięć RAM, ale ma swoje zastosowania. Na razie jednak najlepiej o tym zapomnij.

const char text[] = "This is some text";
Majenko
źródło
6
Warto zauważyć, że na Arduinos (AVR te oparte na co najmniej), literały łańcuchowe żyć w pamięci RAM, chyba zadeklarować je jak makra PROGMEM, PSTR()albo F(). Dlatego const char text[]nie zużywa więcej pamięci RAM niż const char *text.
Edgar Bonet
Teensyduino i wiele innych nowszych kompatybilnych z arduino automatycznie umieszczają literały łańcuchowe w przestrzeni kodu, więc warto sprawdzić, czy na twojej tablicy jest potrzebny F ().
Craig.Feied
@ Craig.Feied Zasadniczo należy używać F () niezależnie. Ci, którzy nie „potrzebują”, mają tendencję do definiowania go jako zwykłego (const char *)(...)castingu. Brak rzeczywistego efektu, jeśli płyta go nie potrzebuje, ale duża oszczędność, jeśli następnie przeniesiesz swój kod do płyty, która tego potrzebuje.
Majenko
5

Aby rozwinąć doskonałą odpowiedź Makenko, istnieje dobry powód, dla którego kompilator ostrzega o tym. Zróbmy szkic testowy:

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

Mamy tutaj dwie zmienne, foo i bar. Zmieniam jeden z tych w setup (), ale widzę, jaki jest wynik:

Thos is some text
Thos is some text

Oni obaj dostał zmieniło!

W rzeczywistości, jeśli spojrzymy na ostrzeżenia, zobaczymy:

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

Kompilator wie, że jest to podejrzane i ma rację! Powodem tego jest to, że kompilator (rozsądnie) oczekuje, że stałe łańcuchowe się nie zmieniają (ponieważ są stałymi). Dlatego jeśli "This is some text"wielokrotnie odwołujesz się do stałej ciągu w swoim kodzie, możesz przydzielić tę samą pamięć do nich wszystkich. Teraz, jeśli zmodyfikujesz jeden, zmodyfikujesz wszystkie!

Nick Gammon
źródło
Święty dym! Kto by wiedział… Czy nadal dotyczy to najnowszych kompilatorów ArduinoIDE? Właśnie wypróbowałem to na ESP32 i powoduje to powtarzające się błędy GuruMeditation .
not2qubit
@ not2qubit Właśnie przetestowałem na Arduino 1.8.9 i jest to prawda.
Nick Gammon
Ostrzeżenia są z jakiegoś powodu. Tym razem dostałem: ostrzeżenie: ISO C ++ zabrania konwertowania stałej ciągu na „char ” [-Wwrite-strings] char bar = "To jest jakiś tekst"; - ZABRONIONE to mocne słowo. Ponieważ nie wolno ci tego robić, kompilator może swobodnie przesuwać i udostępniać ten sam ciąg znaków na dwóch zmiennych. Nie rób rzeczy zabronionych ! (Przeczytaj także i usuń ostrzeżenia). :)
Nick Gammon
Na wypadek, gdybyś natknął się na taki głupi kod i chcesz przetrwać ten dzień. Czy wstępna deklaracja *fooi *barużycie różnych ciągów ciągów zapobiegnęłoby temu? W jaki sposób różni się to od niewprowadzania żadnych ciągów, takich jak char *foo;:?
not2qubit
1
Różne stałe może pomóc, ale osobiście nie byłoby umieścić coś tam, i tam umieścić dane w zwykły sposób później (np. Z new, strcpyi delete).
Nick Gammon
4

Albo przestań próbować przekazać stałą ciągu, w którym funkcja przyjmuje wartość char*, albo zmień funkcję, aby zajęła wartość const char*zamiast.

Ciąg jak „ciąg losowy” to stałe.

Ignacio Vazquez-Abrams
źródło
Czy tekst taki jak „losowe znaki” to stały znak?
Federico Corazza
1
Literały łańcuchowe są stałymi łańcuchowymi.
Ignacio Vazquez-Abrams
3

Przykład:

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

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

void loop ()
  {
  }  // end of loop

Ostrzeżenie:

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

Funkcja foooczekuje znaku * (który może zatem zmodyfikować), ale przekazujesz literał łańcuchowy, którego nie należy modyfikować.

Kompilator ostrzega, aby tego nie robić. Nieaktualne może zmienić się z ostrzeżenia w błąd w przyszłej wersji kompilatora.


Rozwiązanie: Ustaw foo jako const char *:

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

Nie rozumiem Czy masz na myśli, że nie można modyfikować?

Starsze wersje C (i C ++) pozwalają pisać kod tak jak mój przykład powyżej. Możesz stworzyć funkcję (jak foo), która wypisze coś, co do niej przekażesz, a następnie przekaże ciąg literalny (np. foo ("Hi there!");)

Jednak funkcja, która przyjmuje char *jako argument, może modyfikować swój argument (tj. Hi there!W tym przypadku modyfikować ).

Być może napisałeś na przykład:

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

Niestety, przekazując literał, potencjalnie zmodyfikowałeś teraz literał tak, aby „Cześć!” jest teraz „Do widzenia”, co nie jest dobre. W rzeczywistości, jeśli skopiowałeś dłuższy ciąg, możesz zastąpić inne zmienne. Lub w niektórych implementacjach może dojść do naruszenia zasad dostępu, ponieważ „Cześć!” mógł zostać umieszczony w pamięci RAM tylko do odczytu (chronionej).

Dlatego autorzy kompilatorów stopniowo przestają stosować to użycie, więc funkcje, do których przekazujesz literał, muszą zadeklarować ten argument jako const.

Nick Gammon
źródło
Czy to problem, jeśli nie używam wskaźnika?
Federico Corazza
Jaki problem? To szczególne ostrzeżenie dotyczy konwertowania stałej łańcucha na wskaźnik char *. Czy możesz rozwinąć?
Nick Gammon
@Nick: Co masz na myśli mówiąc „(..) podajesz literał ciąg, którego nie należy modyfikować”. Nie rozumiem Masz na myśli can notbyć zmodyfikowany?
Mads Skjern
Zmodyfikowałem swoją odpowiedź. Majenko omówił większość tych punktów w swojej odpowiedzi.
Nick Gammon
1

Mam ten błąd kompilacji:

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

                         ^

Proszę zamienić ten wiersz:
#define TIME_HEADER "T" // Header tag for serial time sync message

z tą linią:
#define TIME_HEADER 'T' // Header tag for serial time sync message

i kompilacja idzie dobrze.

Gin
źródło
3
Ta zmiana zmienia definicję z ciągu
jednoznakowego