устаревшее преобразование из строковой константы в 'char *'

16

Что означает эта ошибка? Я не могу решить это никак.

предупреждение: не рекомендуется преобразовывать строковую константу в 'char *' [-Wwrite-strings]

Федерико Корацца
источник
Этот вопрос должен касаться StackOverflow, а не Arduino :)
Виджей Чавда

Ответы:

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";. Опять буквальное значение: «Создать переменную с именем« text », которая будет указателем только для чтения на эти данные в постоянной памяти». Обратите внимание, что и указатель, и данные теперь доступны только для чтения. Нет ошибок, нет предупреждений. Что произойдет, если мы попробуем запустить нашу тестовую команду? Ну, мы не можем. Компилятор теперь интеллектуален и знает, что мы пытаемся сделать что-то плохое:

ошибка: назначение места только для чтения '* (текст + 2u)'

Это даже не скомпилируется. Попытка записи в постоянную память теперь защищена, поскольку мы сказали компилятору, что наш указатель предназначен только для постоянной памяти. Конечно, он не обязательно должен указывать на постоянную память, но если вы укажете на память чтения-записи (RAM), эта память все равно будет защищена от записи компилятором.

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

ошибка: назначение места только для чтения '* (текст + 2u)'

Итак, краткое описание того, где мы находимся:

Эта форма является полностью недействительной и ее следует избегать любой ценой. Это открывает двери для всех видов плохих вещей, происходящих:

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 и многие другие более поздние arduino-совместимые автоматически размещают строковые литералы в пространстве кода, поэтому стоит проверить, нужен ли F () на вашей плате.
Craig.Feied
@ Craig.Feied В общем случае F () следует использовать независимо. Те, кому это «не нужно», как правило, определяют его как простое (const char *)(...)приведение. Нет реального эффекта, если плате это не нужно, но большая экономия, если вы затем перенесете свой код на плату, которая в этом нуждается.
Маженко
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

Либо прекратите пытаться передать строковую константу, где функция принимает значение 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быть изменены?
Мадс Скьерн
Я изменил свой ответ. Маженко осветил большинство из этих пунктов в своем ответе.
Ник Гэммон
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