Как работают строковые указатели в Arduino?
Насколько я понимаю, когда я заявляю
String s = "This is a string.";
Происходит следующее: на стеке выделяется место для указателя, который указывает на некий объект String, который внутри содержит массив char. Мое замешательство возникает из-за того, что этот указатель, похоже (я могу ошибаться), ведет себя иначе, чем любой другой тип указателя. Например, если я пишу
int a = 1;
int* x = &a;
int* y = x;
происходит то, что указатель, сохраненный в y, теперь указывает на то же место в памяти, что и указатель, сохраненный в x. Так что если я напишу
String s1 = "This is a string.";
String s2 = "Hello World!";
s1 = s2;
указывает ли указатель, сохраненный в s1, на ту же ячейку памяти, что и s2? Или s1 копирует массив символов из s2 в свою собственную отдельную память? Кроме того, если я пишу
String s1 = "This is a string.";
s1 = "Hello World!";
указывает ли s1 теперь на произвольную ячейку памяти, созданную для строкового литерала "Hello World!"? Будет ли эта ячейка памяти освобождена позже, или есть какой-то умный сборщик мусора, который знает, что я использую эту память в куче? Наконец, следует ли мне никогда не присваивать строку, объявленную в стеке, глобальной переменной, так как это будет ссылка на ячейку памяти, которая скоро освободится?
Заранее спасибо за помощь!
@Samuel S, 👍3
Обсуждение1 ответ
Лучший ответ:
String — это не простой тип, как int или char. Это класс со множеством функций-членов и, что более важно, операторов. Когда вы создаете объект, он выделяет место для этого объекта либо в стеке (для локальной переменной), либо в области глобальных данных, если это глобальная переменная. Однако этот объект не содержит памяти, используемой для хранения фактических строковых данных — у него есть только указатель. Память для строковых данных выделяется в куче с помощью malloc() и realloc().
Присвоение строковых данных объекту String выполняется с помощью оператора =, определенного в классе, у которого есть ряд перегруженных разновидностей:
String & String::operator = (const String &rhs)
{
if (this == &rhs) return *this;
if (rhs.buffer) copy(rhs.buffer, rhs.len);
else invalidate();
return *this;
}
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
String & String::operator = (String &&rval)
{
if (this != &rval) move(rval);
return *this;
}
String & String::operator = (StringSumHelper &&rval)
{
if (this != &rval) move(rval);
return *this;
}
#endif
String & String::operator = (const char *cstr)
{
if (cstr) copy(cstr, strlen(cstr));
else invalidate();
return *this;
}
String & String::operator = (const __FlashStringHelper *pstr)
{
if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
else invalidate();
return *this;
}
Все они, в конечном счете, вызывают функции copy() или move(), которые копируют данные из источника в память, выделенную в куче (увеличивая выделение при необходимости).
Использование:
String s1 = "Foo";
String s2 = s1;
s1 имеет выделенную в куче память, затем Foo\0 копируется в нее. s2 затем также имеет выделенную в куче память, и данные из s1 копируются в нее. Таким образом, у вас есть два отдельных объекта с отдельной памятью, выделенной для их содержимого. Итак, делаем:
s2 += "bar";
Вы получаете:
s1: "Foo\0"
s2: "Foobar\0"
Поскольку это объект, размещенный в стеке (или в глобальном пространстве), а не указатель на объект, вы можете получить указатель на него, например, с помощью int:
String s1 = "Foo";
String *s2 = &s1;
(*s2) += "bar";
Что дает вам только один объект и одно пространство памяти, но два имени для него, и результат:
s1: "Foobar\0"
s2: "Foobar\0"
Вот почему важно использовать указатели или ссылки при передаче объектов String в качестве параметров функциям, а постоянное выделение и перераспределение в куче — вот почему объекты String не одобряются в микроконтроллерах с очень ограниченным объемом оперативной памяти.
> Важно использовать указатели или ссылки при передаче строковых объектов в качестве параметров функциям. Было бы очень полезно обновить это замечательное объяснение, добавив несколько примеров того, как передавать/не передавать строки в func!, @Jay Marm
- Выделение строковой памяти Arduino
- Как исправить код утечки памяти в ESP8266/NodeMCU, вызванный концентрацией строк?
- Вычислить SHA256 строки и вывести в строку
- Преобразование беззнакового целого числа в указатель const char
- Чтение из SPIFFS - Как лучше всего работать со строковым (или char) массивом с неопределенной длиной?
- Преобразование строки c integer в unsigned char
- Почему EEPROM.get() не работает?
- Динамическое изменение стека вызовов с помощью указателей
Рекомендую изучить https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/WString.cpp#L214. Выражение
s1 = s2вызовет перегруженный оператор присваивания, который, в зависимости от используемой версии C++, выполнитmove()илиcopy. Выражения видаs1 = "Some String value";приведут к копированию (см.operator = (const char *cstr)). См. также https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/, @Maximilian Gerhardt