Почему поведение объекта различается при назначении ему локальной и глобальной области/класса?

В приведенном ниже коде объект WiFiClient меняет поведение в зависимости от того, присвоен ли он переменной с локальной или нелокальной областью.

Локально:

#include <WiFi.h>

char ssid[] = "[redacted]";    // your network SSID (name)
char pass[] = "[redacted]";        // your network password
int status = WL_IDLE_STATUS;
WiFiServer server(80);

void setup()
{
    Serial.begin(115200);
    while (status != WL_CONNECTED) {
        status = WiFi.begin(ssid, pass);
        delay(5000);
    }
   
    server.begin();
}


void loop()
{
    WiFiClient client = server.available();
    if (client) {
        Serial.println("new client connected");
        String currentLine = "";
        while (client.connected()) {
            if (client.available()) {
                char c = client.read();
                Serial.write(c);
                if (c == '\n') {
                    if (currentLine.length() == 0) {
                        //do something
                    } else {
                        currentLine = "";
                    }
                } else if (c != '\r') {
                    currentLine += c;
                }
            }
        }
        client.stop();
        Serial.println("client disconnected");
    }
}

Вывод (когда клиент подключается):

// execution resumes here from blocking server.available() after a client connects ...

[INFO] A client connected to this server :
[PORT]: 58620
[IP]:192.168.2.36

new client connected
GET / HTTP/1.1
Host: 192.168.2.172
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,fil;q=0.8
Cookie: __guid=259319114.1308697201864418800.1719734660203.9114

Нелокальная область видимости:

#include <WiFi.h>

char ssid[] = "[redacted]";    // your network SSID (name)
char pass[] = "[redacted]";        // your network password
int status = WL_IDLE_STATUS;
WiFiServer server(80);
WiFiClient client;

void setup()
{
    Serial.begin(115200);
    while (status != WL_CONNECTED) {
        status = WiFi.begin(ssid, pass);
        delay(5000);
    }
   
    server.begin();
}

void loop()
{
    client = server.available();
    if (client) {
        Serial.println("new client connected");
        String currentLine = "";
        while (client.connected()) {
            if (client.available()) {
                char c = client.read();
                Serial.write(c);
                if (c == '\n') {
                    if (currentLine.length() == 0) {
                        //do something
                    } else {
                        currentLine = "";
                    }
                } else if (c != '\r') {
                    currentLine += c;
                }
            }
        }
        client.stop();
        Serial.println("client disconnected");
    }
}

Вывод (когда клиент подключается):

// execution resumes here from blocking server.available() after a client connects ...

[INFO] A client connected to this server :
[PORT]: 51193
[IP]:192.168.2.36

new client connected
client disconnected

[INFO] A client connected to this server :
[PORT]: 51192
[IP]:192.168.2.36

new client connected
client disconnected

[INFO] A client connected to this server :
[PORT]: 51194
[IP]:192.168.2.36

new client connected
client disconnected

// ...continues indefinitely

Согласно документации Arduino для server.available(), поведение, наблюдаемое, когда WiFiClient не локально ограничено, не является нормальным, и я уже сообщил о проблеме разработчикам используемого SDK. Однако в целом мне хотелось бы понять, почему назначение объекта в разных областях видимости в C/C++ может привести к такому изменению поведения.

Помимо помещения WiFiClient в глобальную переменную, как указано выше, я также попытался сделать ее членом класса. Я также пробовал разные вещи, чтобы заставить его работать, например, сохранение указателя или ссылки на объект WiFiClient, но ничего из этого не сработало.

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

Очевидно, что более конкретное обсуждение того, почему произошло то, что я испытал выше, потребует глубоких знаний платформы Arduino и SDK, который я использую, так что я спрашиваю не об этом. Мне бы очень хотелось узнать более концептуально, почему такая проблема может возникнуть вообще.


Дополнительная информация: я забыл упомянуть, что server.available() блокируется — хотя существует неблокирующий режим, он здесь не включен, чтобы еще больше исключить проблему, вызванную этим режимом. Проблемы возникают при самой первой поездке через loop(). Выполнение останавливается на WiFiClient client = server.available()/client = server.available(), пока клиент не подключится. Выходы последовательного монитора, которые я включаю, запускаются сразу после подключения клиента. Я также отредактировал эти результаты, чтобы сделать это более понятным.

Функция loop вызывается в цикле системным кодом Arduino. Поэтому, когда client является локальной переменной, она будет создаваться и уничтожаться каждый раз при вызове функции. Если он глобальный, то он не будет уничтожен между вызовами.

Some programmer dude 27.07.2024 14:01

Деструкторы вызываются в конце } блока с ограниченной областью действия. Проблема не имеет ничего общего с Arduino — именно так работает C++. while (whatever) { T x; } совершенно отличается от T x; while (whatever) { }. Первый имеет x живым, а второй (как уже упоминалось) создает и уничтожает x на каждой итерации. Если деструктор выполняет различные действия, влияющие на работу x, то неудивительно, что все работает странно.

PaulMcKenzie 27.07.2024 14:15

Существует также разница в назначении копирования/перемещения с client = server.available(); и с WiFiClient client = server.available(); вызовом конструктора.

Jarod42 27.07.2024 14:26
char ssid[] = ""; и char pass[] = ""; выглядят подозрительно; они оба создают массив из 1 char, который явно недостаточно велик, чтобы содержать SSID или пароль. Недостаточно кода, чтобы увидеть, как они используются, но если где-то зарыт strcpy, он будет писать за пределами границ.
Pete Becker 27.07.2024 14:35

Я подозреваю, что WiFiClient неправильно реализует правило 5/3/0, поэтому удаление перемещенного временного объекта неправильно влияет на экземпляр, который все еще жив.

Jarod42 27.07.2024 16:10

@PaulMcKenzie @Someprogrammerdude Я просто хотел добавить дополнительный контекст: server.available() блокируется до тех пор, пока клиент не подключится, поэтому он никогда не достигает завершения } до того, как произойдет ошибка (пожалуйста, поправьте меня, если я ошибаюсь). Он останавливается на client = server.available();, пока клиент не подключится. Если это так, разве это не должно работать как обычно (по крайней мере, в самом первом цикле, до вызова деструкторов)?

Robert Ernest Dela Cruz 27.07.2024 16:47

@PeteBecker, извините, если это было неясно в моем сообщении, но char ssid[] = ""; и char pass[] = ""; удалены из моего SSID и пароля.

Robert Ernest Dela Cruz 27.07.2024 16:49

@RobertErnestDelaCruz — я не понимаю, что ты имеешь в виду. Но чтобы прояснить мою точку зрения, каждый из этих двух массивов может содержать не более 1 символа.

Pete Becker 27.07.2024 17:19

@PeteBecker - Разве указание размера массива символов при инициализации не является обязательным? См. соответствующую часть документа по агрегатной инициализации.

Robert Ernest Dela Cruz 27.07.2024 18:01

@RobertErnestDelaCruz - я не уверен, где именно в этой документации вы ищете. char x[] = "abcd"; определяет x как массив char из 5 элементов; инициализатор "abcd" (который имеет 5 символов, включая завершающий нуль) определяет размер массива. char x[] = ""; определяет x как массив char с 1 элементом (поскольку "" имеет 1 символ, завершающий нуль).

Pete Becker 27.07.2024 18:04

@PetBecker Ах да. поэтому я извинился за неясность своего поста. char ssid[] = ""/char[] pass = "" так происходит только потому, что я отредактировал (удалил) значения, которые там были изначально (мой собственный SSID и пароль). Они заполнены, как и должно быть, в моем собственном коде. Еще раз извините, если я неясно выразился тогда и раньше.

Robert Ernest Dela Cruz 27.07.2024 18:08

@RobertErnestDelaCruz - хорошо, они инициализируются значениями, которые используются повсюду. Так что нет проблем. Совет: используйте const char ssid[] = ""; для данных персонажа, которые не будут изменены.

Pete Becker 27.07.2024 18:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
12
112
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Между версиями есть как минимум 2 различия (которые связаны между собой):

  1. В локальной версии следующая строка:

    WiFiClient client = server.available();
    

    вызывает конструктор WiFiClient. Но в глобальной версии эта строка:

    client = server.available();
    

    вызывает оператор присваивания (перемещение/копирование), который потенциально отличается.

  2. Функция loop вызывается в цикле системным кодом Arduino.
    Каждый раз он вызывает новый экземпляр этой строкой:

    WiFiClient client = server.available();
    

    Тогда каждый раз, когда функция loop возвращает значение, область действия client также заканчивается и она уничтожается.

    Это отличается от глобальной версии, где всегда используется один и тот же экземпляр client (но, как упоминалось выше, он назначается при каждом вызове loop). Обратите внимание, что в глобальном случае для каждого присваивания создается временный объект (а затем уничтожается).

Оба различия (конструкция и присвоение, а также многократное уничтожение client) на самом деле являются аспектами одной и той же причины — локальной/глобальной области видимости.

«Это отличается от глобальной версии, в которой когда-либо создается только один экземпляр клиента». Временный создается и удаляется сразу после назначения.

Jarod42 27.07.2024 16:07

@Jarod42 Джарод42, ты прав, исправил мой ответ.

wohlstad 27.07.2024 16:11

Спасибо за понимание. Это дает мне дополнительную информацию о потенциальных проблемах и является своего рода указателем, который я искал, чтобы указать мне правильное направление. Но я просто хотел некоторых разъяснений: та же идея справедлива и для локальных и классовых областей, не так ли? Потому что я попробовал это и получил те же результаты. Кроме того, может ли строительство и/или разрушение временного объекта вызвать потенциальные проблемы, даже если он не завершен на момент первого задания (первая поездка через loop()) — актуально из-за блокировки server.available().

Robert Ernest Dela Cruz 27.07.2024 17:25

@RobertErnestDelaCruz (1) относительно области действия класса: не понимаю, что вы имеете в виду. В любом случае член класса жив, пока жив объект класса, и уничтожается, пока жив объект. (2) строительство/разрушение временного объекта может вызвать проблемы, если он не будет должным образом соблюдать правило 0/3/5.

wohlstad 27.07.2024 20:43

@wohlstad Не знал, спасибо! Я проголосовал за ваш ответ.

Robert Ernest Dela Cruz 31.07.2024 03:19

Другие вопросы по теме

Похожие вопросы

Созданные текстуры ломаются в SDL2 при изменении размера окна
C++ для бета-распространения
Почему алгоритмы STL без ранжирования не ограничены концепциями C++20?
Могу ли я написать библиотеку для предварительной загрузки на C++? Есть ли что-нибудь, что мне нужно сделать, кроме добавления `extern "C"` к функциям для перехвата?
Почему clang думает, что у меня есть конструктор копирования?
Почему типы выходят из пространства имен при включении после заголовка вектора?
Инициализация вектора с помощью класса, который имеет параметр конструктора передачи по ссылке и сохраняет эту ссылку как член
Как я могу использовать VSCode с CMake и иметь разные цели, каждая из которых имеет разную архитектуру и набор инструментов?
Неточные вычисления последовательности Фибоначчи во время компиляции в рекурсивной лямбда-выражении
Синхронизируйте три потока в C++ один за другим