Локальный символ* - сохраняет свое значение
Извините за вопрос новичка в Arduino, но я пытаюсь понять, почему res
сохраняет свое значение при повторном вызове up_cmd0
.
например, если num=8
в первом запуске и результат: up_cmd0_res: 8
, а во втором запуске num=12
, результат: up_cmd0_res: 812
void up_cmd0(){
char num[8];
char *res = "up_cmd0_res:";
itoa(counter2, num, 10);
strcat(res ,num);
client.publish(inTopic,res);
@Guy . D, 👍1
4 ответа
Лучший ответ:
char *res = "up_cmd0_res:";
В принципе, res
должен быть const char *
. Const, потому что это
указывая на буквальную строку, и вы не можете (по крайней мере, вы не
предполагается) изменить литеральную строку. Компилятор должен предупредить вас о
что... если включить предупреждения компилятора.
strcat(рез,число);
Это очень неправильно. strcat()
ожидает в качестве своего первого аргумента указатель
в изменяемый массив, достаточно большой для результирующей конкатенации
(и завершающий \0
). Другими словами, вам нужно выделить достаточно
место для всей строки, затем заполните начало выделенного
массив с первой подстрокой, затем вызвать только strcat()
:
char res[24];
strcpy(res, "up_cmd0_res:");
strcat(res, num);
Произошло следующее: изначально в вашей памяти было что-то вроде этого:
<----- memory reserved for the string literal ---->
'u' 'p' '_' 'c' 'm' 'd' '0' '_' 'r' 'e' 's' ':' NUL
^-- res
При первом вызове stracat()
вы перезаписываете завершающий NUL и
в конечном итоге
<----- memory reserved for the string literal ---->
'u' 'p' '_' 'c' 'm' 'd' '0' '_' 'r' 'e' 's' ':' '8' NUL
^-- res
Обратите внимание, что новый завершающий NUL теперь занимает байт в памяти, который
не был выделен для этой цели. Вы, вероятно, перезаписали некоторые
глобальная переменная. В следующий раз, когда вы вызовете strcat()
, вы снова замените
завершаем NUL и продолжаем перезаписывать все, что хранится
в этой части ОЗУ. Ожидайте сбой или неправильное поведение программы.
Потому что вы меняете строковый литерал. Эти литералы находятся в статической памяти, поэтому они сохраняются между вызовами функций.
Вдобавок ко всему, их изменение приводит к неопределенному поведению.
Вы также пишете за пределами строки.
Моя рекомендация:
void up_cmd0(){
const size_t int_max_digits = floor(log10(pow(2, 8 * sizeof(int) - 1))) + 1;
char num[int_max_digits + 2]; // максимальное количество цифр + знак минус + нулевой терминатор
const char *str = "up_cmd0_res:";
char buffer[strlen(str) + sizeof(num)] = {};
strcat(buffer, str);
itoa(counter2, num, 10);
strcat(buffer, num);
// ...
}
Не следует использовать глобальные переменные для передачи значений из одной функции в другую (counter2
), попробуйте передать их в качестве параметра функции.
Локальной переменной является только указатель, *res
. Сама строка "up_cmd0_res:"
находится в другом месте оперативной памяти, хранится как литерал и не предназначена для изменения.
Ваш вызов strcat() перезаписывает (расширяет) литерал каждый раз, когда вы его вызываете, что, кстати, означает, что растущая строка будет перезаписывать что-то еще — все, что следовало за строковым литералом в глобальной памяти!!
Вам необходимо предоставить буфер символов в вашей функции, достаточно большой, чтобы вместить "up_cmd0_res:" плюс самую длинную вещь, которую вы когда-либо могли бы к нему присоединить, плюс еще один байт для нулевого терминатора. Этот буфер должен быть повторно инициализирован при каждой записи функции, прежде чем вы что-либо к нему добавите. Затем выполните вызов strcat для присоединения к нему. (Эти два вызова будут управлять терминатором для вас).
Тот факт, что эта программа не рухнула (или рухнула?), является всего лишь случайностью. Если вы вызовете эту функцию в том виде, в котором она изначально написана, достаточное количество раз, ваш строковый литерал будет расти, пока не перезапишет что-то важное и либо не выдаст неверные результаты (перезаписав некоторые другие данные, либо рухнет (перезаписав стек, особенно адрес возврата текущей функции).
нет, он не разбился, @Guy . D
Со временем так и будет., @Nick Gammon
Неправильно strcat
в поле, указывающее на литерал. Код ниже короче, понятнее и не портит память:
char res [20];
sprintf (res, "up_cmd0_res:%i", counter2);
client.publish(inTopic,res);
Поле res
должно быть достаточно длинным, чтобы вместить буквенную строку (которая при этом не изменяется), а также число, которое вы добавляете в конец, и завершающий байт 0x00.
можете ли вы объяснить, что такое «буквальный»?
ОК. Литерал — это когда вы буквально помещаете строку в переменную. Например:
char *res = "up_cmd0_res:";
Так что res
не указывает на какую-то переменную, а указывает на строку-литерал в коде. Теперь посмотрите на это:
void setup() {
Serial.begin (115200);
char * a = "foo";
Serial.println (a);
strcpy (a, "bar");
Serial.println (a);
char * b = "foo";
Serial.println (b);
}
void loop()
{
}
Вы ожидаете, что этот код напечатает:
foo
bar
foo
Так и должно быть, верно?, потому что переменная b
содержит «foo».
Однако на самом деле он печатает:
foo
bar
bar
Это потому, что strcpy
изменил литеральную строку ("foo"), чтобы теперь она содержала "bar". Компилятор достаточно умен, чтобы думать, что каждый раз, когда он видит "foo" в коде, он может поместить один участок памяти с "foo" в нем, потому что это литерал (а литералы не изменяются). Но теперь вы изменили это.
Компилятор выдает предупреждение:
/tmp/arduino_modified_sketch_905830/sketch_aug19a.ino:7:12: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
char * b = "foo";
^
Представьте, если бы вы могли изменить букву 5 так, чтобы каждый раз, когда вы прибавляете 5 к чему-то, на самом деле прибавлялось 42? Это было бы не так уж здорово, правда?
Ваш код был хуже, потому что вы не просто заменили строку на что-то такой же длины, вы использовали strcat
, который заменяет ее на что-то более длинное.
Спасибо за ответ. Не могли бы вы объяснить, что такое «буквальный»?, @Guy . D
- Как разделить входящую строку?
- Как вывести несколько переменных в строке?
- форматирование строк в Arduino для вывода
- Очень простая операция Arduino Uno Serial.readString()
- DateTime в строку
- Как преобразовать строку в массив байтов
- Как отправить строку на мастер с помощью i2c
- Создание форматированной строки (включая числа с плавающей запятой) в Arduino-совместимом C++
спасибо за ваш ответ - не могли бы вы объяснить, в каких случаях мне следует использовать
char*
вместоchar
? и почему определениеres
в начале кода как предопределенного значения - в начале функции не переопределяет его значение?, @Guy . D@Guy.D: 1. Re
char*
vschar[]
: это непростая тема, но она подробно освещена в разделе [Массивы и указатели](http://c-faq.com/aryptr/) FAQ по C. Прочитайте его: там собраны ответы на все вопросы, которые вы здесь задаете. 2. Re def дляres
не переопределяет его значение:res
не является строкой, он определен как _адрес_ и инициализируется в начале функции в том месте в ОЗУ, где компилятор сохранил анонимный строковый литерал. Смотрите также другие ответы., @Edgar Bonet