Глобальный или локальный

Я новичок в Arduino и встраиваемых системах, но понимаю, что зачастую лучше использовать глобальные, а не локальные переменные (например, здесь или здесь).

У меня есть этот простой код (из здесь ):

//Библиотеки
#include <DHT.h>;

//Константы
#define DHTPIN 2     // к какому выводу мы подключены
#define DHTTYPE DHT22   // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE); //// Инициализируем датчик DHT для обычного Arduino 16 МГц


//Переменные
float hum;  //Сохраняет значение влажности
float temp; //Сохраняет значение температуры

void setup()
{
    Serial.begin(9600);
    dht.begin();

}

void loop()
{
    //Чтение данных и сохранение их в переменных hum и temp
    hum = dht.readHumidity();
    temp= dht.readTemperature();
    //Вывод значений температуры и влажности на последовательный монитор
    Serial.print("Humidity: ");
    Serial.print(hum);
    Serial.print(" %, Temp: ");
    Serial.print(temp);
    Serial.println(" Celsius");
    delay(2000); //Задержка 2 сек.
}

Моя древняя подготовка программиста C (серверы Unix) говорит, что я должен переместить переменные hum и temp в цикл(), или в Arduino лучше оставить их как глобальные значения?

, 👍2

Обсуждение

Это зависит от того, как вы определяете «лучше». Например, когда необходим «глобальный»? Когда «местный» может работать так же хорошо?, @Mikael Patel

Поскольку процессор AVR представляет собой систему с большим количеством регистров, помещение переменных в цикл вполне может привести к тому, что компилятор скажет: «О, у меня достаточно регистров для хранения этой информации. Мне не нужны эти переменные в памяти, так как они сразу же выбрасываются». после их использования. Я удалю их и вместо этого просто буду использовать внутренние регистры.". Оптимизация - это здорово :), @Majenko

Если вы древний программист на C, то попробуйте функции с параметрами «по ссылке». С помощью этого компилятор может выполнить дополнительную оптимизацию. Передача глобальной переменной по ссылке для компилятора почти то же самое, что использование глобальной переменной в функции, но с параметром по ссылке все равно выглядит хорошо, поскольку используемые переменные красиво объявляются как параметры. Кстати, дхт22 не точен, попробуйте что-нибудь получше., @Jot

@Jot - это дешево! И мой проект не критичен. Или есть что-то лучше в той же ценовой категории?, @minisaurus

Влажность, которую возвращает dht22, не является точной, это всего лишь показатель, который можно использовать для проверки повышения или понижения влажности. Когда она составляет 60%, относительная влажность может составлять от 40% до 80%. Хорошие датчики с i2c по-прежнему имеют погрешность 2–3 %. К таким датчикам относятся, например, bme280, bme680, htu21d, sht31-d, si7021. Эти датчики имеют напряжение 3,3 В, а Arduino Uno — 5. Возможно, вам понадобятся переключатели уровня для шины i2c. По температуре ds18b20 — лучший выбор, хороший и дешевый., @Jot

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


3 ответа


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

9

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

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

Иногда переменная используется во многих функциях. Даже если ты сможешь это сделать local, передавая его через параметры функции, делая это не делает программу более читабельной. В этом случае вы также можете предпочесть чтобы сохранить его глобальным.

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

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

,

Спасибо за все ваши ответы, я изменил код, и приятно знать, что мне не нужно слишком сильно менять свои методы :) (Глобальные переменные были запрещены во всех средах разработки, в которых я работал в 1990-х годах). Я прав, что мне НУЖНО использовать глобальную переменную только в том случае, если я ссылаюсь на нее в setup() и цикле()? Все остальное может быть локальным и распространяться?, @minisaurus

@minisaurus: Да, верно. Однако теперь, когда запрет снят, я советую вам стараться не думать о глобальных переменных как о _зле_. Если вы обнаружите, что передаете переменную через множество функций, подумайте о том, чтобы сделать ее глобальной: не повредит ли это читабельности программы? Если ответ «нет», то глобальный вариант, вероятно, будет хорошим выбором., @Edgar Bonet

Я не в курсе, но действительно ли запрет снят в профессиональных средах с несколькими разработчиками, API, библиотеками и неизвестно сколькими файлами исходного кода? Но я понимаю, что глобальные переменные не являются большой проблемой в этих небольших проектах Arduino, состоящих из одного файла., @minisaurus

@minisaurus: Я не знаю об этих средах. Мой ответ касается именно тех программ, которые вы пишете на небольших встроенных устройствах., @Edgar Bonet


2

Общее правило хорошей практики — сводить область действия переменной к необходимому минимуму. Пишите код так, чтобы его можно было читать и поддерживать, не делайте преждевременной оптимизации.

Переменные hum и temp следует определять только в цикле(). Компилятор может оптимизировать его, помещая их в глобальное пространство или используя только регистры MCU, чтобы избежать повторного создания в стеке.

,

0

Не ответ, но я думаю, что некоторые из вас могут быть заинтересованы в стандартах кодирования C некоторых проектов 1990-х годов (на основе стандартов EDS, если я правильно помню).

>

Краткое описание правил:

  • Нет перехода
  • Нет глобальных переменных
  • Все функции (включая основные) возвращают целое число, которое является УСПЕХОМ или ОШИБКОЙ
  • Все переменные, объявленные в начале функции
  • Все переменные объявлены в отдельной строке
  • Нет «встроенной» инициализации переменных.
  • Все переменные инициализируются, обычно нулевым значением, включая malloc.
  • Нет встроенных if
  • Наверное, еще что-то, чего я не помню.

Пример программы:

#include <stdio.h>
#include <stdlib.h>

#define SUCCESS 0
#define FAILURE !SUCCESS

int log_error(char *msg) {
  int rv;

  rv = SUCCESS;

  fprintf(stderr, "%s\n", msg);

  return(rv);
}

int function2(void) {
  int rv;

  rv = SUCCESS;

  return(rv);
}

int function1(void) {
  int rv;
  int i;
  char *mem;

  rv = FAILURE;
  i = 0;
  mem = 0;

  if(NULL == (mem = malloc(400))) {
    log_error("Malloc error");
  } else {
    for(i=0; i<400; i++) {
      mem[i] = 0;
    }
    if(SUCCESS != (rv = function2())) {
      log_error("function2");
    } else {
      rv = SUCCESS;
      free(mem);
    }
  }
  return(rv);
}

int main(int argc, char **argv) {
  int rv;

  rv = FAILURE;

  if(SUCCESS != (rv = function1())) {
    log_error("function1 error");
  }

  return(rv);
}

Немного отличается от того, как K&R написала UNIX, и, возможно, не слишком подходит для встроенных систем? Я не уверен, какой из них мне больше нравится, но, думаю, оба стиля имеют свое место в зависимости от состава команды :)

,

Вы очень мудро заметили, что не все эти правила универсальны. Для иллюстрации: [стиль кодирования ядра Linux](https://www.kernel.org/doc/html/v4.10/process/coding-style.html) противоречит нескольким из вышеперечисленных, включая запреты на gotos (раздел 7) и глобальные переменные (раздел 4), необходимость всегда возвращать код состояния (раздел 16) и инициализация переменной внутри объявления (пример в разделе 7)., @Edgar Bonet