Локальный символ* - сохраняет свое значение

Извините за вопрос новичка в 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);

, 👍1


4 ответа


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

7

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 и продолжаем перезаписывать все, что хранится в этой части ОЗУ. Ожидайте сбой или неправильное поведение программы.

,

спасибо за ваш ответ - не могли бы вы объяснить, в каких случаях мне следует использовать char* вместо char? и почему определение res в начале кода как предопределенного значения - в начале функции не переопределяет его значение?, @Guy . D

@Guy.D: 1. Re char* vs char[]: это непростая тема, но она подробно освещена в разделе [Массивы и указатели](http://c-faq.com/aryptr/) FAQ по C. Прочитайте его: там собраны ответы на все вопросы, которые вы здесь задаете. 2. Re def для res не переопределяет его значение: res не является строкой, он определен как _адрес_ и инициализируется в начале функции в том месте в ОЗУ, где компилятор сохранил анонимный строковый литерал. Смотрите также другие ответы., @Edgar Bonet


2

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

Вы также пишете за пределами строки.

Моя рекомендация:

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), попробуйте передать их в качестве параметра функции.

,

6

Локальной переменной является только указатель, *res. Сама строка "up_cmd0_res:" находится в другом месте оперативной памяти, хранится как литерал и не предназначена для изменения.

Ваш вызов strcat() перезаписывает (расширяет) литерал каждый раз, когда вы его вызываете, что, кстати, означает, что растущая строка будет перезаписывать что-то еще — все, что следовало за строковым литералом в глобальной памяти!!

Вам необходимо предоставить буфер символов в вашей функции, достаточно большой, чтобы вместить "up_cmd0_res:" плюс самую длинную вещь, которую вы когда-либо могли бы к нему присоединить, плюс еще один байт для нулевого терминатора. Этот буфер должен быть повторно инициализирован при каждой записи функции, прежде чем вы что-либо к нему добавите. Затем выполните вызов strcat для присоединения к нему. (Эти два вызова будут управлять терминатором для вас).

Тот факт, что эта программа не рухнула (или рухнула?), является всего лишь случайностью. Если вы вызовете эту функцию в том виде, в котором она изначально написана, достаточное количество раз, ваш строковый литерал будет расти, пока не перезапишет что-то важное и либо не выдаст неверные результаты (перезаписав некоторые другие данные, либо рухнет (перезаписав стек, особенно адрес возврата текущей функции).

,

нет, он не разбился, @Guy . D

Со временем так и будет., @Nick Gammon


4

Неправильно 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