Как работают строковые указатели в 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 не одобряются в микроконтроллерах с очень ограниченным объемом оперативной памяти.
- Выделение строковой памяти Arduino
- Как исправить код утечки памяти в ESP8266/NodeMCU, вызванный концентрацией строк?
- Вычислить SHA256 строки и вывести в строку
- Преобразование беззнакового целого числа в указатель const char
- Чтение из SPIFFS - Как лучше всего работать со строковым (или char) массивом с неопределенной длиной?
- Преобразование строки c integer в unsigned char
- Почему EEPROM.get() не работает?
- Динамическое изменение стека вызовов с помощью указателей