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

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

warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]

, 👍19

Обсуждение

Этот вопрос должен быть на StackOverflow, а не на Arduino :), @Vijay Chavda


5 ответов


Лучший ответ:

29

По своему обыкновению, я собираюсь предоставить немного справочной технической информации о причинах возникновения этой ошибки.

Я рассмотрю четыре различных способа инициализации строк 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», чтобы получилось «Это какой-то текст». Во всех случаях (как вы думаете) этого можно было бы достичь за счет:

text[2] = 'o';

Теперь давайте посмотрим, что делает каждый способ объявления строки и как это выражение text[2] = 'o'; повлияет на ситуацию.

Первый наиболее часто встречающийся способ: char *text = "Это какой-то текст";. Что это означает буквально? Ну, в C это буквально означает «Создайте переменную с именем text, которая является указателем для чтения-записи на этот строковый литерал, который хранится в пространстве (коде) только для чтения». Если у вас включен параметр -Wwrite-strings, вы получите предупреждение, как показано в вопросе выше.

В основном это означает "Предупреждение: вы попытались сделать переменную, доступную для чтения и записи, указывающей на область, в которую вы не можете записывать". Если вы попробуете, а затем установите третий символ в «o», вы на самом деле попытаетесь записать в область только для чтения, и все будет не очень хорошо. На традиционном ПК с Linux это приводит к:

Ошибка сегментации

Теперь второй: char text[] = "Это какой-то текст";. Буквально в C это означает: «Создайте массив типа «char» и инициализируйте его данными «Это какой-то текст\0». Размер массива будет достаточно большим для хранения данных». Таким образом, фактически выделяется ОЗУ и копируется в нее значение «Это какой-то текст\0» во время выполнения. Никаких предупреждений, никаких ошибок, совершенно верно. И как это сделать правильно, если вы хотите иметь возможность редактировать данные. Попробуем запустить команду text[2] = 'o':

Это какой-то текст

Это сработало, отлично. Хорошо.

Теперь третий способ: const char *text = "Это какой-то текст";. Снова буквальное значение: «Создайте переменную с именем «текст», которая является указателем только для чтения на эти данные в памяти только для чтения». Обратите внимание, что и указатель, и данные теперь доступны только для чтения. Никаких ошибок, никаких предупреждений. Что произойдет, если мы попытаемся запустить нашу тестовую команду? Ну, мы не можем. Компилятор теперь умный и знает, что мы пытаемся сделать что-то плохое:

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

Он даже не скомпилируется. Попытка записи в память только для чтения теперь защищена, потому что мы сообщили компилятору, что наш указатель указывает на память только для чтения. Конечно, не обязательно указывать на память только для чтения, но если вы укажете на память для чтения и записи (ОЗУ), эта память все равно будет защищена от записи компилятором. .

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

ошибка: присвоение местоположения только для чтения '*(text + 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";
,

Стоит отметить, что на Arduinos (по крайней мере, на базе AVR) строковые литералы находятся в оперативной памяти, если только вы не объявите их с помощью макроса типа PROGMEM, PSTR() или F(). Таким образом, const char text[] не использует больше оперативной памяти, чем const char *text., @Edgar Bonet

Teensyduino и многие другие более поздние версии, совместимые с Arduino, автоматически помещают строковые литералы в пространство кода, поэтому стоит проверить, нужен ли F() на вашей плате., @Craig.Feied

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


5

Либо перестаньте пытаться передать строковую константу, где функция принимает char*, либо измените функцию так, чтобы вместо этого она принимала const char*.

Строки типа "случайная строка" являются константами.

,

Является ли текст типа «случайные символы» постоянным символом?, @Federico Corazza

Строковые литералы — это строковые константы., @Ignacio Vazquez-Abrams


4

Пример:

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

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo ("bar");
  }  // конец настройки

void loop ()
  {
  }  // конец цикла

Предупреждение:

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

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

Компилятор предупреждает вас не делать этого. Будучи устаревшим, оно может превратиться из предупреждения в ошибку в будущей версии компилятора.


Решение: заставить foo принимать const char *:

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

Я не понимаю. Вы имеете в виду, что нельзя изменить?

Старые версии C (и C++) позволяют писать код, аналогичный моему примеру выше. Вы можете создать функцию (например, foo), которая печатает то, что вы ей передаете, а затем передает литеральную строку (например, foo ("Привет!");)

Однако функция, которая принимает char * в качестве аргумента, может изменять свой аргумент (т. е. в данном случае изменять Привет!).

Вы могли написать, например:

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

К сожалению, передавая литерал, вы теперь потенциально можете изменить этот литерал так, что "Привет!" теперь «До свидания», что нехорошо. На самом деле, если вы скопируете более длинную строку, вы можете перезаписать другие переменные. Или в некоторых реализациях вы получите нарушение прав доступа, потому что «Привет!» мог быть помещен в доступную только для чтения (защищенную) оперативную память.

Поэтому составители компиляторов постепенно устаревают такое использование, так что функции, которым вы передаете литерал, должны объявите этот аргумент как const.

,

Это проблема, если я не использую указатель?, @Federico Corazza

Что за проблема? Это конкретное предупреждение касается преобразования строковой константы в указатель char*. Можете ли вы уточнить?, @Nick Gammon

@Ник: Что значит «(..) вы передаете строковый литерал, который не следует изменять». Я не понимаю. Вы имеете в виду «нельзя» изменить?, @Mads Skjern

Я изменил свой ответ. Маженко осветил большую часть этих вопросов в своем ответе., @Nick Gammon


6

Чтобы уточнить отличный ответ Макенко, есть веская причина, по которой компилятор предупреждает вас об этом. Сделаем тестовый скетч:

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

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  foo [2] = 'o';     // изменить только foo
  Serial.println (foo);
  Serial.println (bar);
  }  // конец настройки

void 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, и это правда., @Nick Gammon

Предупреждения существуют по какой-то причине. На этот раз я получил: *предупреждение: ISO C++ запрещает преобразование строковой константы в 'char*' [-Wwrite-strings] char *bar = "Это какой-то текст";* - ЗАПРЕЩЕНО - сильное слово. Поскольку вам запрещено это делать, компилятор может свободно возиться и использовать одну и ту же строку для двух переменных. Не делайте *запрещенных* вещей! (Также прочтите и устраните предупреждения). :), @Nick Gammon

Итак, на случай, если вы столкнетесь с таким дрянным кодом и захотите пережить этот день. Поможет ли первоначальное объявление *foo и *bar с использованием **разных** строк *"констант"*, чтобы это не произошло? Кроме того, чем это отличается от отсутствия каких-либо строк вообще, например: char *foo;?, @not2qubit

Различные константы могут помочь, но лично я бы не стал помещать туда *ничего*, а потом помещать туда данные обычным способом (например, с помощью new, strcpy и delete)., @Nick Gammon


2

У меня есть эта ошибка компиляции:

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

                         ^

Замените эту строку:
#define TIME_HEADER "T" // Тег заголовка для последовательного сообщения синхронизации времени

с этой строкой:
#define TIME_HEADER 'T' // Тег заголовка для последовательного сообщения синхронизации времени

и компиляция проходит успешно.

,

Это изменение изменяет определение со строки из одного символа «T» на один символ со значением кода ASCII для заглавной буквы T., @dlu