Как работают строковые указатели в Arduino?

string memory pointer object-oriented

Насколько я понимаю, когда я заявляю

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!"? Будет ли эта ячейка памяти освобождена позже, или есть какой-то умный сборщик мусора, который знает, что я использую эту память в куче? Наконец, следует ли мне никогда не присваивать строку, объявленную в стеке, глобальной переменной, так как это будет ссылка на ячейку памяти, которая скоро освободится?

Заранее спасибо за помощь!

, 👍3


1 ответ


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

8

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 не одобряются в микроконтроллерах с очень ограниченным объемом оперативной памяти.

,