застаріле перетворення з константного рядка в "char *"


16

Що означає ця помилка? Я не можу це вирішити жодним чином.

попередження: застаріле перетворення з постійної константи в 'char *' [-Wwrite-string]


Це питання має бути на StackOverflow, а не на Arduino :)
Vijay Chavda

Відповіді:


26

Як і моє бажання, я збираюся надати трохи довідкової технічної інформації в те, чому і чому ця помилка.

Я збираюся ознайомитись з чотирма різними способами ініціалізації рядків C і побачити, які відмінності між ними. Це чотири способи, про які йдеться:

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

Тепер для цього я хочу змінити третю букву "i" на "o", щоб це було "Thos - це якийсь текст". Це у всіх випадках (можна подумати) можна досягти:

text[2] = 'o';

Тепер давайте розглянемо, що робить кожен спосіб оголошення рядка і як це text[2] = 'o';твердження вплине на речі.

По- перше, найбільш часто бачили шлях: char *text = "This is some text";. Що це буквально означає? Ну, в C це буквально означає "Створити змінну, яку називають textвказівником читання-запису на цей рядковий літерал, який зберігається у просторі для коду (код)." Якщо у вас -Wwrite-stringsвключена опція, ви отримуєте попередження, як видно з питання вище.

В основному це означає "Попередження: Ви намагалися зробити змінну, яка вказує на запис читання, до області, до якої ви не можете записати". Якщо ви спробуєте, а потім встановите третій символ на "o", ви насправді намагатиметесь записати в область лише для читання, і все буде непогано. На традиційному ПК з Linux, що призводить до:

Помилка сегментації

Тепер другий один: char text[] = "This is some text";. Буквально в C це означає "Створити масив типу" char "та ініціалізувати його з даними" Це деякий текст \ 0 ". Розмір масиву буде досить великим для зберігання даних". Таким чином, це фактично виділяє оперативну пам’ять і копіює в неї значення "Це деякий текст \ 0". Ніяких попереджень, жодних помилок, абсолютно справедливі. І правильний спосіб зробити це, якщо ви хочете мати можливість редагувати дані . Спробуємо виконати команду text[2] = 'o':

Тхос - це якийсь текст

Це спрацювало, ідеально. Добре.

Тепер третій шлях: const char *text = "This is some text";. Знову буквальне значення: "Створіть змінну під назвою" текст ", яка є вказівником лише для читання на ці дані в пам'яті, що використовується лише для читання." Зауважте, що і вказівник, і дані тепер доступні лише для читання. Ні помилок, ні попереджень. Що станеться, якщо ми спробуємо виконати тестову команду? Ну, ми не можемо. Зараз компілятор розумний і знає, що ми намагаємось зробити щось погане:

помилка: присвоєння місця лише для читання "* (текст + 2у)"

Він навіть не компілюється. Намагання записати в пам'ять, лише для читання, тепер захищена, оскільки ми сказали компілятору, що наш покажчик - це пам'ять лише для читання. Звичайно, це не повинні бути вказуючи на пам'ять тільки для читання, але якщо ви вказуєте для читання-запису пам'яті (RAM) , що пам'ять по- , як і раніше буде захищений від записуються компілятором.

Нарешті остання форма: const char text[] = "This is some text";. Знову ж таки, як і раніше, []він виділяє масив в ОЗУ і копіює в нього дані. Однак зараз це масив лише для читання. Ви не можете записати на нього, оскільки вказівник на нього позначений як const. Спроба написати до нього призводить до:

помилка: присвоєння місця лише для читання "* (текст + 2у)"

Отже, короткий підсумок того, де ми знаходимося:

Ця форма абсолютно недійсна і її слід уникати будь-якою ціною. Це відкриває двері для всіляких поганих речей:

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

6
Варто зазначити, що на Arduinos (принаймні на основі AVR) рядкові літерали живуть в оперативній пам'яті, якщо ви не оголосите їх з подібним макросом PROGMEM, PSTR()або F(). Таким чином, const char text[]не використовується більше оперативної пам'яті, ніж const char *text.
Едгар Бонет

Teensyduino та багато інших останніх ардуїно-сумісних автоматично розміщують рядкові літерали у просторі коду, тому варто перевірити, чи потрібен F () на вашій дошці.
Craig.Feied

@ Craig.Feied Загалом F () слід використовувати незалежно. Ті, хто не потребує цього, зазвичай визначають це як простий (const char *)(...)кастинг. Немає реального ефекту, якщо плата цього не потребує, але велика економія, якщо ви потім портуєте свій код на плату.
Majenko

5

Щоб пояснити чудову відповідь Макенко, є вагома причина, чому компілятор попереджає вас про це. Давайте зробимо тестовий ескіз:

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

У нас є дві змінні, foo та bar. Я видозмінюю одну з таких у setup (), але подивіться, що це результат:

Thos is some text
Thos is some text

Вони обоє змінилися!

Насправді, якщо ми подивимось на попередження, які ми бачимо:

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

Компілятор знає, що це хитро, і це правильно! Причиною цього є те, що компілятор (розумно) очікує, що константи рядків не змінюються (оскільки вони є константами). Таким чином, якщо ви посилаєтесь на постійну постійну "This is some text"множину разів у своєму коді, дозволено виділяти однакову пам'ять всім їм. Тепер якщо ви модифікуєте один, ви модифікуєте їх усі!


Святий дим! Хто міг би знати ... Це все-таки справедливо для останніх компіляторів ArduinoIDE? Я просто спробував це на ESP32, і це викликає повторні помилки GuruMeditation .
not2qubit

@ not2qubit Я щойно тестував на Arduino 1.8.9, і це правда.
Нік Гаммон

Попередження є чомусь причиною. Цього разу я отримав: попередження: ISO C ++ забороняє перетворювати рядкову константу в char "[-Wwrite-strings] char bar =" Це текст "; - ЗАБОРОНЕННЯ - сильне слово. Оскільки вам заборонено це робити, компілятор вільно може обмінятись та поділитися тією ж ланцюжкою над двома змінними. Не робіть заборонених речей! (Також прочитайте та усуньте попередження). :)
Нік Гаммон

Тож у випадку, якщо ви натрапите на такий хитрий код, і хочете пережити день. Чи не дозволить початкове оголошення *fooта *barвикористання різних рядкових "констант" цього не статися? Крім того, чим це відрізняється від того, що взагалі не вставляти жодних рядків, наприклад char *foo;:?
not2qubit

1
Різні константи можуть допомогти, але особисто я б не ставив нічого там, і покласти туди дані звичайним способом пізніше (наприклад, з new, strcpyа delete).
Нік Гаммон

4

Або перестаньте намагатися передати рядкову константу, де функція приймає a char*, або змініть функцію, щоб вона const char*замість неї взяла .

Рядок типу "випадковий рядок" - константи.


Чи такий текст, як "випадкові символи", є постійним символом?
Федеріко Кораца

1
Лінійні рядки - це струнні константи.
Ігнасіо Васкес-Абрамс

3

Приклад:

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

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

void loop ()
  {
  }  // end of loop

Увага:

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

Функція fooочікує знаку * (який, отже, може змінювати), але ви передаєте літеральний рядок, який не слід змінювати.

Компілятор попереджає вас цього не робити. Якщо застаріло, це може перетворитися з попередження в помилку в майбутній версії компілятора.


Рішення: Зробіть foo взяти const char *:

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

Я не розумію. Ви маєте на увазі, що неможливо змінити?

Старіші версії C (і C ++) дозволяють писати код, як мій приклад вище. Ви можете зробити функцію (на зразок foo), яка друкує те, що ви передаєте їй, а потім передає буквальний рядок (наприклад, foo ("Hi there!");)

Однак функції, яка береться char *за аргумент, дозволено змінювати свій аргумент (тобто модифікувати Hi there!в цьому випадку).

Можливо, ви написали, наприклад:

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

На жаль, передаючи вниз літерал, ви потенційно змінили цей буквал так, що "Привіт там!" зараз "Прощавай", що не є добре. Насправді, якщо ви скопіювали у довший рядок, ви можете перезаписати інші змінні. Або в деяких реалізаціях ви отримаєте порушення доступу, оскільки "Привіт!" можливо, було поставлено оперативну пам'ять, доступну лише для читання.

Тож автори-компілятори поступово знецінюють це використання, так що функції, на які ви передаєте буквальний, повинні оголошувати цей аргумент якconst .


Це проблема, якщо я не використовую вказівник?
Федеріко Кораца

Яка проблема? Це особливе попередження стосується перетворення рядкової константи в покажчик char *. Чи можете ви докладно?
Нік Гаммон

@ Nick: Що ви маєте на увазі "(..) ви передаєте літеральний рядок, який не слід змінювати". Я не розумію. Ви маєте на увазі can notбути модифікованим?
Mads Skjern

Я змінив свою відповідь. Маєнко висвітлював більшість цих пунктів у своїй відповіді.
Нік Гаммон

1

У мене така помилка компіляції:

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

                         ^

Будь ласка, замініть цей рядок:
#define TIME_HEADER "T" // Header tag for serial time sync message

за допомогою цього рядка:
#define TIME_HEADER 'T' // Header tag for serial time sync message

і складання йде добре.


3
Ця зміна змінює визначення з одно символьного рядка "T" на єдиний символ зі значенням коду ASCII для літери T
dlu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.