• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Помогите разобраться с String vs char[]

Patriot

New member
Ребят, помогите разобраться со строкой и массивом чаров.
Код простой:
Код:
struct MqttConfig {
    char host[50] = "192.168.1.94";
    unsigned short port = 1883;
    char user[10] = "mqtt";
    char password[20] = "password";
    char client[20] = "LightController";
};

MqttConfig mqttConfig;
WiFiClient espClient;
PubSubClient mqttClient(espClient);

mqttClient.setServer(mqttConfig.host, configmqttConfig.port);
Но mqtt библиотека не подключается (да, там дальше вызов mqttClient.connect, это для компактности). Методом тыка, понял, что дело в mqttConfig.host , если его заменить (и остальные user/password) на явные: mqttClient.setServer("192.168.1.94", 1883); то все работает

Почему так происходит и как быть? Если выводить Serial.println(mqttConfig.host); то там тот же хост, что и указан. В общем, я в тупике. Структура мне нужна чтобы хранить настройки в EEPROM.
 

enjoynering

Well-known member
Открываем исходники библиотеки PubSubClient по адресу - https://github.com/knolleary/pubsubclient/blob/master/src/PubSubClient.h
и что же мы там видим в строке 133, 134, 135? А вот что:
Код:
   PubSubClient& setServer(IPAddress ip, uint16_t port);
   PubSubClient& setServer(uint8_t * ip, uint16_t port);
   PubSubClient& setServer(const char * domain, uint16_t port);
из кода очевидно что у вас не правильно задан IP. вот как надо
Код:
IPAddress host(192.168.1.94);
 

Patriot

New member
Открываем исходники библиотеки PubSubClient по адресу - https://github.com/knolleary/pubsubclient/blob/master/src/PubSubClient.h
и что же мы там видим в строке 133, 134, 135? А вот что:
Код:
   PubSubClient& setServer(IPAddress ip, uint16_t port);
   PubSubClient& setServer(uint8_t * ip, uint16_t port);
   PubSubClient& setServer(const char * domain, uint16_t port);
из кода очевидно что у вас не правильно задан IP. вот как надо
Код:
IPAddress host(192.168.1.94);
Т.е. когда я делаю так: mqttClient.setServer("192.168.1.94", 1883);, то "192.168.1.94" автоматически приводится к типу IPAddress?
Но тогда почему работает и такой вариант:
C++:
const char* host = "192.168.1.94";
mqttClient.setServer(host2, config.getMqtt().port);
??? Тоже срабатывает приведение типов?
 

enjoynering

Well-known member
вам бы матчасть подтянуть, вы плаваете.

Т.е. когда я делаю так: mqttClient.setServer("192.168.1.94", 1883);, то "192.168.1.94" автоматически приводится к типу IPAddress?
нет она приводится к типу:
Код:
 PubSubClient& setServer(const char * domain, uint16_t port);

Но тогда почему работает и такой вариант const char* host = "192.168.1.94"; ?
потому что приводится к типу:
Код:
PubSubClient& setServer(const char * domain, uint16_t port);
 

Patriot

New member
вам бы матчасть подтянуть, вы плаваете.
Это да, этим и занимаюсь - учусь сразу на домашнем проекте.


нет она приводится к типу:
Код:
 PubSubClient& setServer(const char * domain, uint16_t port);
потому что приводится к типу:
Код:
PubSubClient& setServer(const char * domain, uint16_t port);
Так, хорошо. А как тогда привести char host[50] = "192.168.1.94" к const char * ?
Ведь субъективо:
char host[50] = "192.168.1.94"; и const char* host = "192.168.1.94";
Содержат одни и те же данные. Я даже пробовал делать что-то вроде этого:

Код:
    int sizeS = 0;
    for (int i = 0; i < 50; i++) {
        if (mqttConfig.host[i] == '\0') {
            sizeS = i;
            break;
        }
    }
    char host[sizeS];
    strcpy(host, config.getMqtt().host);
 

nikolz

Well-known member
Это да, этим и занимаюсь - учусь сразу на домашнем проекте.
А читать учебники не пробовали?
Ну хотя бы вики:
В языках программирования C, C ++, D, JavaScript и Julia const - это классификатор типа: ключевое слово, применяемое к типу данных, которое указывает, что данные доступны только для чтения. Хотя это может использоваться для объявления констант, const в семействе языков C отличается от аналогичных конструкций в других языках тем, что является частью типа, и поэтому имеет сложное поведение в сочетании с указателями, ссылками, составными типами данных и проверкой типов.
----------------
или у майкрософт:
Квалификаторы типов
  • Статья
  • 02.05.2022
  • Чтение занимает 3 мин
Квалификаторы типов предоставляют идентификатору одно из двух свойств. Квалификатор типа const объявляет объект как неизменяемый. Квалификатор типа volatile объявляет элемент, значение которого можно изменить допустимым образом с помощью средств, недоступных программе, в которой он находится, таких как выполняемый в данный момент поток.

Квалификаторы типов const , restrict и volatile могут использоваться в объявлении только один раз. Квалификаторы типов могут использоваться с любым описателем типа; они не могут находиться после первой запятой в объявлении нескольких элементов. Например, следующие объявления допустимы.
 

enjoynering

Well-known member
так же читал (не знаю правда или нет), что const переменные меньше жрут память.

вот это определение просто жесть:
Квалификатор типа volatile объявляет элемент, значение которого можно изменить допустимым образом с помощью средств, недоступных программе, в которой он находится, таких как выполняемый в данный момент поток.
такое только в русских учебниках нати можно - отдельно слова понятны, но смысла в предложении ноль. самое печальное, что если открыть англиский учебник, там написанно так, что поймет даже ребенок.
 

rst

Member
так же читал (не знаю правда или нет), что const переменные меньше жрут память.
Тут больше всего подходит выражение "Слышал звон, да не знаю где он." :cool:
Инициализированные переменные требуют своей инициализации startup-кодом программы. Инициализации тем или иным способом. Если это программа, находящаяся о флешь МК, то инициализация обычно производится копированием начального значения переменной из флешь в ОЗУ. Соответственно кроме того, что переменная занимает место в ОЗУ, нужно ещё где-то во флешь хранить её начальное значение.
Если же имеем дело с константой, объявленной с квалификатором const, то очевидно что для неё место в ОЗУ резервировать не нужно, и можно её держать во флешь и оттуда же и использовать.

такое только в русских учебниках нати можно - отдельно слова понятны, но смысла в предложении ноль. самое печальное, что если открыть англиский учебник, там написанно так, что поймет даже ребенок.
А что именно тут непонятно? Вроде всё кристалльно ясно. И смысл есть и он правильный.

PS: Можно просто Вы не понимаете, что такое volatile? Или неправильно поняли его назначение из того самого англ.учебника, который хвалите? :cool:
 

rst

Member
Ребят, помогите разобраться со строкой и массивом чаров.
Код простой:
Если это си/си++, то разберитесь что такое "объявление типа", а что такое "определение переменной/константы/объекта заданного типа". В учебниках по си это разжёвано.
Можно например так:
C-like:
struct MqttConfig {
  char host[50];
  unsigned short port;
  char user[10];
  char password[20];
  char client[20];
};

static MqttConfig const mqttConfig1 = {
  "192.168.1.94",
  1883,
  "mqtt",
  "password",
  "LightController"
};
А ещё лучше (если компилятор позволяет) так:
Код:
struct MqttConfig {
  char host[50];
  unsigned short port;
  char user[10];
  char password[20];
  char client[20];
};

static MqttConfig const mqttConfig2 = {
  .host = "192.168.1.94",
  .port = 1883,
  .user = "mqtt",
  .password = "password",
  .client = "LightController"
};
 

enjoynering

Well-known member
Инициализированные переменные требуют своей инициализации startup-кодом программы. Инициализации тем или иным способом.
Это все понятно, теорию я тоже читал. Дьявол кроется в деталях. Меня интересует вот такие два случая? Будет ли экономия heap в первом примере по сравнению со вторым?

Код:
foo(const uint8_t var)
{
  //brilliant code here
}
Код:
foo(uint8_t var)
{
  //brilliant code here
}
Можно просто Вы не понимаете, что такое volatile?
Знаю и понимаю. Но вот этот набор русских слов никак не проливает свет НОВИЧКУ на то, что такое volatile, а только ещё больше вгоняет его в ступор и безнадегу.

Уже даже не 10-й раз сталкиваюсь с адскими объяснением в советских учебниках. Открываю английский учебник и все понятно с первого раза, а английский у меня так себе. Как так?
 

rst

Member
Меня интересует вот такие два случая? Будет ли экономия heap в первом примере по сравнению со вторым?
Про какие случаи речь? И при чём тут heap?

Знаю и понимаю. Но вот этот набор русских слов никак не проливает свет НОВИЧКУ на то, что такое volatile, а только ещё больше вгоняет его в ступор и безнадегу.
Плох тот новичок, который пытается понять чего-то по одной фразе. И не ищет других источников.
 

enjoynering

Well-known member
Вы мне щас экзамен устраиваете? Хорошо. Запрещаю изменять значение переменной var внутри функции foo(). Мой вопрос все тоже, ответьте пожалуйста с высоты ваших знаний и чётких академических определений - Будет ли экономия heap в первом примере по сравнению со вторым?

Плох тот новичок, который пытается понять чего-то по одной фразе. И не ищет других источников.
Ну так себе отмазка.
 

nikolz

Well-known member
Это все понятно, теорию я тоже читал. Дьявол кроется в деталях. Меня интересует вот такие два случая? Будет ли экономия heap в первом примере по сравнению со вторым?

Код:
foo(const uint8_t var)
{
  //brilliant code here
}
Код:
foo(uint8_t var)
{
  //brilliant code here
}

Знаю и понимаю. Но вот этот набор русских слов никак не проливает свет НОВИЧКУ на то, что такое volatile, а только ещё больше вгоняет его в ступор и безнадегу.

Уже даже не 10-й раз сталкиваюсь с адскими объяснением в советских учебниках. Открываю английский учебник и все понятно с первого раза, а английский у меня так себе. Как так?
попробую объяснить.
Что и сколько жрет памяти Вы определяете указывая тип данных (uint8 - жрет байт)
Другой вопрос - откуда он его жрет.
если Вы укажите строковую константу например "привет"
то она разместится в секции инициализированных переменных и сожрет там 6 символов+1 на конечный ноль т е 7 байт. из кучи она ничего не жрет.
но, если Вы ее перепишите в буфер, то для буфера Вы выделите не менее 7 байт и это уже из кучи (как правило).
В итоге эта константа сожрет 14 байт.
Т е если пользуете константы, то старайтесь их использовать по ссылке без переписки в буфер, тогда лишнего не будет жрать.
Примерно так
 

rst

Member

enjoynering

Well-known member
Какие "примеры"? Не понимаю о чём речь. Задайте вопрос внятно.
ну как бы больше вопросов к вам не имею. с вами вссе понятно. напомнило байку про пофессора электронщика, котоый знал все определения, но не смог починить свой телевизор.

а вот nikolz спасибо прояснил. едиственно не понятно что такое - "секции инициализированных переменных". это стек или флеш МК?
 

rst

Member
Что и сколько жрет памяти Вы определяете указывая тип данных (uint8 - жрет байт)
Не факт. uint8 в аргументах функции - будет "жрать" в зависимости от соглашений вызова, по которым работает данный компилятор. Может сожрать и целый 32-битный регистр.
если Вы укажите строковую константу например "привет"
то она разместится в секции инициализированных переменных и сожрет там 6 символов+1 на конечный ноль т е 7 байт.
Бред. Константы располагаются в секциях констант, а не переменных.
 

enjoynering

Well-known member
Не факт. uint8 в аргументах функции - будет "жрать" в зависимости от соглашений вызова, по которым работает данный компилятор. Может сожрать и целый 32-битный регистр.
Тут я с rst согласен. Например esp8266, как бы 32-битый контроллер и изменение переменных с int32 на int8 не приводит к уменьшению кода на arduino framework. А вот на 8-и битном AVR замена с 16 или 32 на 8 уменьшает размер скетча.
 

nikolz

Well-known member
ну как бы больше вопросов к вам не имею. с вами вссе понятно. напомнило байку про пофессора электронщика, котоый знал все определения, но не смог починить свой телевизор.

а вот nikolz спасибо прояснил. едиственно не понятно что такое - "секции инициализированных переменных". это стек или флеш МК?
Это область кода программы.
На компе - это оперативная память, на ESP - это флеш память.
Когда компоновщик собирает загрузочный файл, то он размещает весь код программы в разные секции.
упрощенно - это секции данных, секция кода, секция отладки и т д
Секции данных делятся на инициализированные т е те, где записаны константы программы те переменные которым вы присвоили значения
и неинициализированные т е те которые вы выделили но ничего им не присвоили
-------------
Это переменые, которые Вы выделяете вне функций т е они глобальные
а также константы внутри функций
----------------
Вы можете сами создать дополнительно секции данных, которые будут размещены в области кода программ.
Например на винде можно создать общедоступную память для всех процессов.
подробнее о структуре кода программ можно почитать в статьях про линковщик
например здесь:
 
Сверху Снизу