Использование глобальных переменных во встроенных системах

17

Я начал писать прошивку для своего продукта, и я новичок здесь. Я просмотрел много статей о том, как не использовать глобальные переменные или функции. Есть ли ограничение на использование глобальных переменных в 8-битной системе или это полное «Нет-Нет». Как я должен использовать глобальные переменные в моей системе или я должен полностью избегать их?

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

Rookie91
источник
Этот вопрос не уникален для встроенных систем. Дубликат можно найти здесь .
Лундин
@Lundin Из вашей ссылки: «В наши дни это имеет значение только во встроенных средах, где память довольно ограничена. Что-то нужно знать, прежде чем предполагать, что встроенные средства такие же, как в других средах, и что правила программирования одинаковы для всех».
Эндолит
Сфера staticдействия файла @endolith - это не то же самое, что «глобальный», см. мой ответ ниже.
Лундин

Ответы:

31

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

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

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. Используйте структуру, чтобы объединить связанные переменные, чтобы было понятнее, где их следует использовать, а где нет.

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
  3. Используйте глобальные статические переменные, чтобы сделать переменные видимыми только в текущем C-файле. Это предотвращает случайный доступ кода в других файлах из-за конфликтов имен.

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */

И последнее замечание: если вы изменяете глобальную переменную в подпрограмме прерывания и читаете ее в другом месте:

  • Отметьте переменную volatile.
  • Убедитесь, что он является атомарным для ЦП (т. Е. 8-разрядным для 8-разрядного ЦП).

ИЛИ

  • Используйте механизм блокировки для защиты доступа к переменной.
richarddonkin
источник
изменчивые и / или атомарные переменные не помогут вам избежать ошибок, вам понадобятся какие-то блокировки / семафоры или кратковременное маскирование прерываний при записи в переменную.
Джон U
3
Это довольно узкое определение «отлично работает». Моя точка зрения заключалась в том, что объявление чего-то изменчивого не предотвращает конфликты. Кроме того, ваш третий пример не очень хорошая идея - наличие двух отдельных глобальных переменных с одинаковыми именами , по крайней мере, усложняет понимание кода.
Джон U
1
@JohnU Вы не должны использовать volatile для предотвращения гонок, ведь это не поможет. Вы должны использовать volatile для предотвращения опасных ошибок оптимизации компилятора, распространенных в компиляторах встроенных систем.
Лундин
2
@JohnU: обычное использование volatileпеременных - позволить коду, выполняющемуся в одном контексте выполнения, дать возможность коду в другом контексте выполнения знать, что что-то произошло. В 8-битной системе буфер, который будет содержать число байтов с степенью двойки не более 128, может управляться одним энергозависимым байтом, указывающим общее количество времени жизни байтов, помещенных в буфер (мод 256), и другой, указывающий количество извлеченных байтов за время жизни, при условии, что только один контекст выполнения помещает данные в буфер, и только один выводит данные из него.
суперкат
2
@JohnU: Хотя для управления буфером можно использовать некоторую форму блокировки или временно отключать прерывания, на самом деле это не нужно и не полезно. Если бы буфер должен был содержать 128-255 байт, кодирование должно было бы измениться незначительно, и если бы он должен был содержать больше, вероятно, было бы необходимо отключение прерываний, но в 8-битной системе буферы склонны быть маленькими; Системы с большими буферами обычно могут делать атомарные записи размером более 8 бит.
суперкат
24

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

Только плохие программисты зацикливаются на правилах типа «не используйте глобальные переменные». Хорошие программисты понимают причину, лежащую в основе правил, а затем относятся к правилам как к руководящим принципам.

Легко ли понять вашу программу? Его поведение предсказуемо? Легко ли изменить его части, не ломая другие? Если ответ на каждый из этих вопросов « да» , значит, вы находитесь на пути к хорошей программе.

Фил Фрост
источник
1
Что сказал @MichaelKaras - важно понимать, что эти вещи значат и как они влияют на вас (и как они могут вас укусить).
Джон U
5

Вы не должны полностью избегать использования глобальных переменных (для краткости «глобальные»). Но вы должны использовать их разумно. Практические проблемы с чрезмерным использованием глобалов:

  • Глобалы видны по всему блоку компиляции. Любой код в модуле компиляции может изменить глобальный. Последствия модификации могут проявиться в любом месте, где этот глобальный объект оценивается.
  • В результате глобальные переменные затрудняют чтение и понимание кода. Программист всегда должен иметь в виду все места, где глобальный объект оценивается и присваивается.
  • Чрезмерное использование глобальных переменных делает код более подверженным дефектам.

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

Ник Алексеев
источник
2
Флаг не должен быть глобальным. ISR может вызвать функцию, которая имеет статическую переменную, например.
Фил Фрост
+1 Я не слышал о такой уловке раньше. Я удалил этот абзац из ответа. Как бы staticфлаг стал видимым для main()? Вы подразумеваете, что та же самая функция, которая имеет, staticможет возвратить это main()позже?
Ник Алексеев
Это один из способов сделать это. Возможно, функция принимает новое состояние для установки и возвращает старое состояние. Есть много других способов. Возможно, у вас есть один исходный файл с функцией для установки флага и другой для его получения со статической глобальной переменной, содержащей состояние флага. Хотя технически это «глобально» по терминологии Си, его область действия ограничена только этим файлом, который содержит только те функции, которые необходимо знать. То есть его сфера не является «глобальной», что на самом деле является проблемой. C ++ предоставляет дополнительные механизмы инкапсуляции.
Фил Фрост
4
Некоторые методы обхода глобальных переменных (например, доступ к оборудованию только через драйверы устройств) могут быть слишком неэффективными для 8-разрядной среды с острой нехваткой ресурсов.
Спехро Пефхани
1
@SpehroPefhany Хорошие программисты понимают причину, лежащую в основе правил, а затем относятся к правилам как к руководящим принципам. Когда рекомендации противоречат друг другу, хороший программист тщательно взвешивает весы.
Фил Фрост
4

Преимущество глобальных структур данных во встроенной работе заключается в том, что они статичны. Если каждая нужная вам переменная является глобальной, то вы никогда не будете случайно исчерпывать память при вводе функций и выделении для них места в стеке. Но тогда, в этот момент, почему есть функции? Почему бы не одна большая функция, которая обрабатывает всю логику и процессы - как BASIC-программа без GOSUB? Если вы достаточно углубитесь в эту идею, у вас будет типичная программа на ассемблере 1970-х годов. Эффективно и невозможно поддерживать и устранять неисправности.

Поэтому используйте разумно глобальные переменные, такие как переменные состояния (например, если каждая функция должна знать, находится ли система в состоянии интерпретации или выполнения) и другие структуры данных, которые должны просматриваться многими функциями и, как говорит @PhilFrost, - это поведение ваши функции предсказуемы? Есть ли возможность заполнить стек входной строкой, которая никогда не заканчивается? Это вопросы для разработки алгоритма.

Обратите внимание, что static имеет различное значение внутри и снаружи функции. /programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

/programming/5033627/static-variable-inside-of-a-function-in-c

К. Таун Спрингер
источник
1
Многие компиляторы встроенных систем распределяют автоматические переменные статически, но перекрывают переменные, используемые функциями, которые не могут одновременно находиться в области видимости; это обычно дает использование памяти, равное возможному использованию в наихудшем случае для стековой системы, в которой фактически могут встречаться все статически возможные последовательности вызовов.
суперкат
4

Глобальные переменные должны использоваться только для действительно глобального состояния. Использование глобальной переменной для представления чего-то вроде, например, широты северной границы карты, будет работать только в том случае, если когда-либо будет только одна «северная граница карты». Если в будущем код, возможно, придется работать с несколькими картами, которые имеют разные северные границы, код, который использует глобальную переменную для северной границы, вероятно, придется переработать.

В типичных компьютерных приложениях часто нет особых оснований полагать, что никогда не будет более одного объекта. Однако во встроенных системах такие допущения часто бывают более разумными. Хотя вполне возможно, что для поддержки нескольких пользователей одновременно может потребоваться типичная компьютерная программа, пользовательский интерфейс типичной встроенной системы будет предназначен для работы одним пользователем, взаимодействующим со своими кнопками и дисплеем. Как таковой, он в любой момент времени будет иметь одно состояние пользовательского интерфейса. Разработка системы таким образом, чтобы несколько пользователей могли взаимодействовать с несколькими клавиатурами и дисплеями, потребовала бы гораздо большей сложности и заняла бы гораздо больше времени, чем ее разработка для одного пользователя. Если система никогда не вызывается для поддержки нескольких пользователей, любые дополнительные усилия, приложенные для облегчения такого использования, будут потрачены впустую. Если, скорее всего, потребуется многопользовательская поддержка, было бы разумнее рискнуть отказаться от кода, используемого для однопользовательского интерфейса в случае необходимости многопользовательской поддержки, чем тратить дополнительное время на добавление многопользовательской поддержки. поддержка пользователей, которая, вероятно, никогда не понадобится.

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

Supercat
источник
Поэтому сохранение глобальных переменных, которые не будут конфликтовать друг с другом, не будет проблемой. Например: Day_cntr, week_cntr и т. д. для подсчета дней и недели соответственно. И я надеюсь, что не следует намеренно использовать большую часть глобальных переменных, которые похожи на одну и ту же цель и должны четко определять их. Большое спасибо за подавляющий ответ. :)
Rookie91
-1

Многие люди смущены этой темой. Определение глобальной переменной:

То, что доступно из любой точки вашей программы.

Это не то же самое, что переменные области файла , которые объявлены ключевым словом static. Это не глобальные переменные, это локальные частные переменные.

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

Вы должны использовать глобальные переменные? Есть несколько случаев, когда это хорошо:

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

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

Лундин
источник
Почему отрицательный голос?
м.Алин
2
Я думаю, что «глобальная переменная» также может использоваться для описания распределений по глобальному сегменту (не тексту, стеку или куче). В этом смысле статические переменные файла и функции являются / могут быть «глобальными». В контексте этого вопроса несколько ясно, что глобальный термин относится к сфере охвата, а не к сегменту распределения (хотя, возможно, ФП не знал этого).
Пол А. Клейтон,
1
@ PaulA.Clayton Я никогда не слышал о формальном термине «сегмент глобальной памяти». Ваши переменные окажутся в одном из следующих мест: регистры или стек (распределение во время выполнения), куча (распределение во время выполнения), сегмент .data (явно инициализированные статические переменные хранения), сегмент .bss (обнуленные статические переменные хранения), .rodata (чтение только константы) или .text (часть кода). Если они оказываются где-то еще, это какая-то конкретная настройка проекта.
Лундин
1
@ PaulA.Clayton Я подозреваю, что то, что вы называете «глобальным сегментом», является .dataсегментом.
Лундин