При записи библиотеки возвращайте строку или возвращайте char *

Я пытаюсь написать свою собственную библиотеку Arduino, и я хотел бы сделать ее полезной не только для плат ESP32/ESP8266, но и для плат Arduino.

Я создал свой заголовочный файл следующим образом

test.cpp

#ifndef TEST
#define TEST
#include "Arduino.h"

#define MAX_STRING_LEN 50

class MyClass{
    private:

    public:
        String getString();
        char * getCharPointer();
        char * getAnotherCharPointer();
};

#endif

и мой

test.cpp

#include "test.h"


static char buffer[MAX_STRING_LEN+1];

String MyClass::getString(){
    // Пример вызова REST API
    String testString = String("{\"test:\" \"hello String json\"}");

    return testString;
}

char * MyClass::getCharPointer(){
    // Пример вызова REST API
    const char * sample_json = "{\"test:\" \"hello Char Pointer json\"}";
    strcpy(buffer, sample_json);
    return buffer;
}

char * MyClass::getAnotherCharPointer(){
    // Пример вызова REST API
    const char * sample_json = "{\"test:\" \"hello Another Char Pointer json\"}";
    strcpy(buffer, sample_json);
    return buffer;
}

Мой тестовый файл main.cpp

main.cpp

#include <Arduino.h>
#include "test.h"

MyClass myClass;
void setup() {
  // поместите сюда код установки для однократного запуска:
  Serial.begin(115200);

  Serial.println(myClass.getString());
  Serial.println(myClass.getCharPointer());
  Serial.println(myClass.getAnotherCharPointer());
}

void loop() {
  // поместите сюда ваш основной код для многократного запуска:
}

Результат в порядке

{"test:" "hello String json"}
{"test:" "hello Char Pointer json"}
{"test:" "hello Another Char Pointer json"}

У меня есть сомнения, и я хотел бы попросить совета.

  1. Вернуть класс String в файлы заголовков — плохая идея? Для ESP32/ESP8266 они подходят, поскольку у них большой объем памяти, но для плат Arduino Uno я вижу много дискуссий о том, чтобы не использовать String, потому что это плохо и вызывает фрагментацию памяти и т. д.
  2. Является ли возврат char * самым безопасным способом сделать его совместимым и полезным для всех плат MCU?
  3. Следующий вопрос об указателе на символ. Я часто вижу этот шаблон при возврате указателя на символ, как в это Time. cpp, в котором статический буфер используется и определяется в файле реализации (test.cpp), а все функции (getCharPointer, getAnotherCharPointer), которые должны возвращать указатель символа, просто манипулируют статическим буфером символов с помощью strcpy. Это правильный путь в программировании Arduino?

Может ли кто-нибудь развеять мои сомнения? Спасибо.

, 👍1

Обсуждение

Хороший вопрос! Я боюсь, что это может быть немного основано на мнении. Мне нравится подход со статическим буфером за его удобство использования памяти. Однако это опасно, если пользователь библиотеки не знает о последствиях этого. Не уверен, что это подходит для начинающих..., @Edgar Bonet

Если все ваши функции возвращают строковый литерал, то просто используйте строковый литерал напрямую, эти функции не нужны. Шаблон, который вы разместили, делает это, потому что строковые литералы хранятся во флэш-памяти (PROGMEM) и, следовательно, должны быть скопированы в ОЗУ, однако это зависит от архитектуры вашего чипа, вам необходимо скопировать из FLASH в RAM для классического AVR чипов (например, ATmega328), однако не требуется для чипов ARM, ESP или даже современных чипов AVR, выпущенных с 2016 года..., @hcheung

@hcheung: я считаю, что эти строковые литералы здесь только для предоставления простых фиктивных примеров, а настоящая библиотека будет строить строки из данных, собранных во время выполнения., @Edgar Bonet

@ЭдгарБонет и другие. Хотя вопрос основан на мнении, в данном случае количество разумных мнений очень ограничено. Пожалуйста, продолжайте и напишите свое мнение в качестве ответа., @Juraj


2 ответа


0

Я часто вижу этот шаблон при возврате указателей на символы, как в этом Time.cpp, где используется статический буфер, определенный в файле реализации (test.cpp) и всех функциях (getCharPointer, getAnotherCharPointer), которые должны возвращать символ. указатель просто манипулирует буфером статических символов с помощью strcpy. Это правильный путь в программировании Arduino?

Программирование на Arduino — это программирование на C++. Конечно, вы можете вернуть указатель на статический буфер. Но пользователь вашей функции должен знать, что если он вызовет функцию во второй раз, данные будут перезаписаны. Таким образом, им нужно будет strcpy (то есть скопировать) данные, прежде чем они будут перезаписаны.

Лично я бы держался подальше от класса String, если только он не используется время от времени. Это нормально со строками, которые всегда имеют одинаковый размер, но если это не так, фрагментация памяти будет проблемой.

,

1

[ОП спрашивает:] Для ESP32/ESP8266 они подходят, поскольку у них большой объем памяти, но для плат Arduino Uno я вижу много дискуссий о том, чтобы не использовать String, потому что это плохо и вызывает фрагментацию памяти и т. д.

На Unos и других платах с очень небольшим объемом памяти фрагментация не займет много времени. На платах с большим объемом памяти это просто займет больше времени!, но это произойдет; это просто разница когда. (И для полноты картины я должен сказать, что выделение нескольких объектов один раз и в пределах доступной свободной памяти устройства не вызовет фрагментации, как и статическое выделение).

Так почему же вообще используется выделение/освобождение [я спрашиваю, риторически...]?

В системах с полной ОС, управляемых нами с помощью клавиатуры и мыши, большинство прикладных программ являются временными — они запускаются, выполняют задание и закрываются; даже более длительные программы, такие как офисные приложения, довольно ограничены в скорости из-за медленных людей. И если программа дает сбой, мы все еще сидим там, чтобы перезапустить ее, и ни одно внешнее устройство не остается зависшим на часы или дни, может быть, разрушая себя (возможно, из-за перегрева), может быть, раня или убивая пациента (в случае медицинского устройства), ранения или смерть многих людей (в случае двигателя транспортного средства или органов управления полетом).

В противном случае выделение памяти во время выполнения удобно для программиста.

Так что же делать во встроенной системе вместо этого [спрашивает я]?

Я использую два метода, чтобы обойти склонность к сбоям распределителя/деаллокатора библиотеки компилятора:

  1. Когда я пишу библиотеку, я разрабатываю ее таким образом, чтобы от вызывающей стороны ожидался (object *), как вы и предполагали в своем "сомнении №3". Все, что нужно сделать библиотеке, — это поместить туда свой результат; память уже будет выделена. Библиотеке не нужно знать или заботиться о том, как она была выделена. Звучит ли это так, будто я избегаю решения проблемы и оставляю проблемы с памятью для решения кого-то другого? ? Да, действительно, и это преднамеренно! Пользователь моей библиотеки — вы (или я, как-нибудь в другой раз), разработчик приложений/архитектор системы — лучше всех предугадывает потребности своей конкретной системы в памяти и решает, как лучше всего их удовлетворить.

    Написание моей библиотеки таким образом позволяет пользователю библиотеки решить, что лучше всего подходит для его приложения: статический буфер, локальный или динамический.

  2. Я пишу собственное управление памятью (вероятно, на уровне приложения, а не в библиотеке). Это почти всегда принимает форму массива буферов фиксированной длины. Независимо от того, сколько памяти запрашивает вызывающая сторона, пока она меньше этого фиксированного размера и доступна, тогда вызов завершается успешно, и буфер будет возвращен; в противном случае вызов завершается ошибкой. Вызывающий не знает или не должен знать, что возвращаемый им буфер может быть больше, чем он запросил.

    Почему этот метод лучше? malloc()/free() и new()/delete() разбивают свободную память на все более мелкие штук, как они просят. Распределитель буфера фиксированного размера не разбивает память.

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

,