konversi usang dari konstanta string ke 'char *'

16

Apa artinya kesalahan ini? Saya tidak bisa menyelesaikannya dengan cara apa pun.

peringatan: konversi yang tidak digunakan lagi dari konstanta string ke 'char *' [-Wwrite-string]

Federico Corazza
sumber
Pertanyaan ini seharusnya ada di StackOverflow, bukan Arduino :)
Vijay Chavda

Jawaban:

26

Seperti biasa saya, saya akan memberikan sedikit informasi teknis latar belakang ke mengapa dan di mana kesalahan ini.

Saya akan memeriksa empat cara berbeda menginisialisasi string C dan melihat apa perbedaan di antara mereka. Inilah empat cara yang dipertanyakan:

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

Sekarang untuk ini saya ingin mengubah huruf ketiga "i" menjadi "o" untuk membuatnya "Thos is some text". Itu bisa, dalam semua kasus (Anda akan berpikir), dicapai dengan:

text[2] = 'o';

Sekarang mari kita lihat apa yang masing-masing cara mendeklarasikan string dan bagaimana text[2] = 'o';pernyataan itu akan memengaruhi banyak hal.

Pertama cara yang paling sering terlihat: char *text = "This is some text";. Apa artinya ini secara harfiah? Nah, dalam C, secara harfiah berarti "Buat variabel yang disebut textyang merupakan pointer baca-tulis ke string literal ini yang diadakan di ruang read-only (kode).". Jika Anda mengaktifkan opsi -Wwrite-stringsmaka Anda akan mendapat peringatan seperti yang terlihat pada pertanyaan di atas.

Pada dasarnya itu berarti "Peringatan: Anda telah mencoba membuat variabel yang titik baca-tulis ke area yang tidak dapat Anda tulis". Jika Anda mencoba dan kemudian mengatur karakter ketiga ke "o" Anda sebenarnya akan mencoba menulis ke area baca-saja dan semuanya tidak akan menyenangkan. Pada PC tradisional dengan Linux yang menghasilkan:

Kesalahan Segmentasi

Sekarang yang kedua: char text[] = "This is some text";. Secara harfiah, dalam C, itu berarti "Buat array bertipe" char "dan inisialisasi dengan data" Ini adalah beberapa teks \ 0 ". Ukuran array akan cukup besar untuk menyimpan data". Sehingga sebenarnya mengalokasikan RAM dan menyalin nilai "Ini adalah beberapa teks \ 0" ke dalamnya saat runtime. Tidak ada peringatan, tidak ada kesalahan, sangat valid. Dan cara yang tepat untuk melakukannya jika Anda ingin dapat mengedit data . Mari kita coba jalankan perintah text[2] = 'o':

Ini adalah beberapa teks

Itu bekerja dengan sempurna. Baik.

Sekarang cara ketiga: const char *text = "This is some text";. Lagi arti literal: "Buat variabel yang disebut" teks "yang merupakan pointer hanya baca untuk data ini dalam memori hanya baca.". Perhatikan bahwa pointer dan data sekarang hanya baca. Tidak ada kesalahan, tidak ada peringatan. Apa yang terjadi jika kita mencoba dan menjalankan perintah pengujian kita? Yah, kita tidak bisa. Kompiler sekarang cerdas dan tahu bahwa kami mencoba melakukan sesuatu yang buruk:

galat: penetapan lokasi hanya-baca '* (teks + 2u)'

Bahkan tidak akan dikompilasi. Mencoba menulis ke memori hanya-baca sekarang dilindungi karena kami telah memberi tahu kompiler bahwa pointer kami adalah memori hanya-baca. Tentu saja, itu tidak memiliki harus menunjuk ke memori hanya-baca, tetapi jika Anda arahkan ke baca-tulis memori (RAM) memori yang masih akan dilindungi dari yang ditulis oleh compiler.

Akhirnya bentuk terakhir: const char text[] = "This is some text";. Sekali lagi, seperti sebelumnya dengan []itu mengalokasikan array dalam RAM dan menyalin data ke dalamnya. Namun, sekarang ini adalah array baca-saja. Anda tidak dapat menulis ke sana karena penunjuknya ditandai sebagai const. Mencoba menulis kepadanya menghasilkan:

galat: penetapan lokasi hanya-baca '* (teks + 2u)'

Jadi, ringkasan singkat dari tempat kami berada:

Formulir ini sepenuhnya tidak valid dan harus dihindari dengan cara apa pun. Ini membuka pintu bagi segala macam hal buruk yang terjadi:

char *text = "This is some text";

Formulir ini adalah formulir yang tepat jika Anda ingin membuat data dapat diedit:

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

Formulir ini adalah formulir yang tepat jika Anda ingin string yang tidak akan diedit:

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

Bentuk ini sepertinya boros RAM tetapi memang ada kegunaannya. Lebih baik lupakan saja untuk saat ini.

const char text[] = "This is some text";
Majenko
sumber
6
Perlu dicatat bahwa pada Arduinos (yang berbasis AVR setidaknya), string literal hidup dalam RAM, kecuali jika Anda mendeklarasikannya dengan makro like PROGMEM, PSTR()atau F(). Dengan demikian, const char text[]tidak menggunakan lebih banyak RAM daripada const char *text.
Edgar Bonet
Teensyduino dan banyak lagi arduino-compatibles terbaru lainnya secara otomatis menempatkan string literal dalam ruang kode, jadi ada baiknya memeriksa untuk melihat apakah F () diperlukan di papan Anda.
Craig. Diisi
@ Craig.Feied Secara umum F () harus digunakan terlepas. Mereka yang tidak "perlu" cenderung mendefinisikannya sebagai (const char *)(...)casting sederhana . Tidak ada efek nyata jika papan tidak membutuhkannya, tetapi penghematan besar jika Anda kemudian porting kode Anda ke papan yang tidak.
Majenko
5

Untuk menguraikan jawaban Makenko yang sangat baik, ada alasan bagus mengapa kompiler memperingatkan Anda tentang ini. Mari kita buat sketsa tes:

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

Kami memiliki dua variabel di sini, foo dan bar. Saya memodifikasi salah satu yang ada di setup (), tetapi lihat hasilnya:

Thos is some text
Thos is some text

Mereka berdua berubah!

Bahkan jika kita melihat peringatan yang kita lihat:

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

Kompiler tahu ini cerdik, dan itu benar! Alasan untuk ini adalah, bahwa kompiler (cukup) mengharapkan konstanta string tidak berubah (karena mereka adalah konstanta). Jadi jika Anda merujuk ke konstanta string "This is some text"beberapa kali dalam kode Anda, itu diperbolehkan untuk mengalokasikan memori yang sama untuk semuanya. Sekarang jika Anda memodifikasi satu, Anda memodifikasi semuanya!

Nick Gammon
sumber
Asap Suci! Siapa yang akan tahu ... Apakah ini masih berlaku untuk kompiler ArduinoIDE terbaru? Saya baru saja mencobanya pada ESP32 dan itu menyebabkan kesalahan GuruMeditation berulang .
not2qubit
@ not2qubit Saya baru saja menguji Arduino 1.8.9 dan memang benar ada.
Nick Gammon
Peringatan ada karena suatu alasan. Kali ini saya mendapat: warning: ISO C ++ melarang konversi string konstan menjadi 'char ' [-Wwrite-strings] char bar = "Ini teks"; - FORBIDS adalah kata yang kuat. Karena Anda dilarang melakukan itu, kompiler bebas untuk berkeliaran dan berbagi string yang sama melalui dua variabel. Jangan lakukan hal-hal terlarang ! (Juga, baca dan hilangkan peringatan). :)
Nick Gammon
Jadi jika Anda menemukan kode jelek seperti ini, dan ingin bertahan hari itu. Akan deklarasi awal dari *foodan *barmenggunakan berbagai tali "konstanta" , mencegah hal ini terjadi? Juga, bagaimana ini berbeda dari tidak meletakkan string sama sekali, seperti char *foo;:?
not2qubit
1
Konstanta yang berbeda mungkin membantu, tetapi secara pribadi saya tidak akan meletakkan apa pun di sana, dan memasukkan data ke sana dengan cara yang biasa nanti (mis. Dengan new, strcpydan delete).
Nick Gammon
4

Entah berhenti mencoba meneruskan string konstan di mana fungsi mengambil char*, atau mengubah fungsi sehingga mengambil const char*alih.

String seperti "string acak" adalah konstanta.

Ignacio Vazquez-Abrams
sumber
Apakah teks seperti "karakter acak" adalah karakter konstan?
Federico Corazza
1
String literal adalah konstanta string.
Ignacio Vazquez-Abrams
3

Contoh:

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

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

void loop ()
  {
  }  // end of loop

Peringatan:

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

Fungsi foomengharapkan char * (yang karenanya dapat dimodifikasi) tetapi Anda melewati string literal, yang tidak boleh dimodifikasi.

Compiler memperingatkan Anda untuk tidak melakukan ini. Menjadi usang itu mungkin berubah dari peringatan menjadi kesalahan dalam versi kompiler masa depan.


Solusi: Buatlah foo take a const char *:

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

Saya tidak mengerti. Maksud Anda tidak dapat dimodifikasi?

Versi C (dan C ++) yang lebih lama memungkinkan Anda menulis kode seperti contoh saya di atas. Anda bisa membuat fungsi (seperti foo) yang mencetak sesuatu yang Anda berikan padanya, dan kemudian meneruskan string literal (mis. foo ("Hi there!");)

Namun fungsi yang char *dianggap sebagai argumen diizinkan untuk mengubah argumennya (mis. Memodifikasi Hi there!dalam kasus ini).

Anda mungkin telah menulis, misalnya:

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

Sayangnya, dengan menurunkan literal, Anda sekarang berpotensi mengubah literal itu sehingga "Hai!" sekarang "Selamat tinggal" yang tidak baik. Bahkan jika Anda menyalin dalam string yang lebih panjang, Anda mungkin menimpa variabel lain. Atau, pada beberapa implementasi Anda akan mendapatkan pelanggaran akses karena "Hai, di sana!" mungkin dimasukkan ke dalam memori read-only (protected).

Jadi penulis kompiler secara bertahap mencela penggunaan ini, sehingga fungsi yang Anda lewatkan secara literal, harus menyatakan argumen itu sebagai const.

Nick Gammon
sumber
Apakah masalah jika saya tidak menggunakan pointer?
Federico Corazza
Masalah apa? Peringatan khusus itu adalah tentang mengubah konstanta string menjadi char * pointer. Bisakah Anda menguraikan?
Nick Gammon
@Nick: Apa maksudmu "(..) kamu melewatkan string literal, yang seharusnya tidak dimodifikasi". Saya tidak mengerti. Apakah maksud Anda can notdiubah?
Mads Skjern
Saya mengubah jawaban saya. Majenko membahas sebagian besar poin ini dalam jawabannya.
Nick Gammon
1

Saya memiliki kesalahan kompilasi ini:

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

                         ^

Silakan ganti baris ini:
#define TIME_HEADER "T" // Header tag for serial time sync message

dengan baris ini:
#define TIME_HEADER 'T' // Header tag for serial time sync message

dan kompilasi berjalan dengan baik.

gin
sumber
3
Perubahan ini mengubah define dari string satu karakter "T" ke karakter tunggal dengan nilai kode ASCII untuk huruf kapital T.
dlu