Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Использование глобальных переменных
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
demiurg1978
Хочу узнать мнение насчет использования глобальных переменных. Спрашиваю вот почему: неоднократно слышал, что использование глобальных переменных нужно максимально минимизировать. Откуда пошло это? В данный момент пишу проект. Свои проекты всегда стараюсь разделить на модули. static переменные. использование в других модулях посредством set_value (); get_value ();. Но в нынешнем проекте у меня много параметров. И если честно, я заколебался на каждую переменную писать свои функции установки и получения переменных. Хочу вывести переменные из static в глобальные.
Ваши за и против.
zltigo
Цитата(demiurg1978 @ Jan 9 2017, 20:26) *
неоднократно слышал, что..

Ну так и спрашиваете там, где "неоднократно слышали".
desh
Цитата(demiurg1978 @ Jan 9 2017, 21:26) *
Хочу вывести переменные из static в глобальные.
Ваши за и против.


Плохая практика. Программа быстро превращается в это
demiurg1978
Цитата(desh @ Jan 10 2017, 02:28) *
...

Скажу честно, с английским настолько не дружу.
zltigo
Цитата(desh @ Jan 9 2017, 22:28) *
Плохая практика.

К чему эти глупые страшилки не по делу.

aiwa
Цитата(demiurg1978 @ Jan 9 2017, 20:26) *
Хочу вывести переменные из static в глобальные.
Ваши за и против.

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

Что касается функций установки и чтения значений переменных, то у меня такая же история: терпения на их написание не хватает и я обычно в приватную секцию вставляю "public:". Но даже в этом случае для тех переменных, тип или значения которых могут измениться в будущем, функции (или псевдофункции) прописываю сразу.
Dima_G
Цитата(demiurg1978 @ Jan 10 2017, 01:26) *
static переменные. использование в других модулях посредством set_value (); get_value ();. Но в нынешнем проекте у меня много параметров. И если честно, я заколебался на каждую переменную писать свои функции установки и получения переменных.


Смысла в простых get/set функциях, тупо устанавливающих значения локальных переменных нет. Проще использовать глобальные переменные.
get/set функции нужны в том случае, если производится какая-то дополнительная работа, помимо установки/возврата переменной. Например
Код
int set_age(int age)
{
  if (age>=0 && age <=100)
  {
    local_age = age;
    return 0;
  }

  perror("incorrect age!");
  return -1;
}


К вопросу о "много параметров", может есть смысл объединить их в структуры?
Непомнящий Евгений
Цитата(demiurg1978 @ Jan 9 2017, 21:26) *
Хочу узнать мнение насчет использования глобальных переменных. Спрашиваю вот почему: неоднократно слышал, что использование глобальных переменных нужно максимально минимизировать. Откуда пошло это? В данный момент пишу проект. Свои проекты всегда стараюсь разделить на модули. static переменные. использование в других модулях посредством set_value (); get_value ();. Но в нынешнем проекте у меня много параметров. И если честно, я заколебался на каждую переменную писать свои функции установки и получения переменных. Хочу вывести переменные из static в глобальные.
Ваши за и против.


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

Просто механически каждую переменную оборачивать в пару функций смысла нет. Но если модулю 1 нужны прям вот все переменные модуля 2, то у вас что-то не так с разделением на модули. Обычно модуль дает какой-то интерфейс, куда входят функции, и, возможно, какой-то набор глобальных переменных.

Опять же, вот пусть у нас есть модуль "передатчик по порту". Если у нас появятся два порта и понадобятся два передатчика - как мы будем выкручиваться? Поэтому обычно тут так или иначе используют ООП - все переменные, которые требуются "передатчику" собираются в структуру, а каждая функция получает указатель на эту структуру (или к примеру числовой дескриптор).

А вообще, дайте конкретный пример sm.gif По нему уже можно дать какие-то рекомендации


Цитата(zltigo @ Jan 9 2017, 23:57) *
К чему эти глупые страшилки не по делу.


Это не то, чтобы страшилки. Обычно, если в коде много GOTO или функции на тысячи строк или все "потроха" торчат наружу, то с ним что-то не так sm.gif Хотя конечно возможны какие-то ситуации, когда все это оправдано
demiurg1978
Цитата(Непомнящий Евгений @ Jan 10 2017, 12:30) *
А вообще, дайте конкретный пример sm.gif По нему уже можно дать какие-то рекомендации

Редактирование параметров в меню.
Сергей Борщ
QUOTE (demiurg1978 @ Jan 10 2017, 09:11) *
Редактирование параметров в меню.
Собрать все параметры в структуру (наверняка это уже сделано для удобства сохранения/чтения да и вообще для повышения читабельности кода). Передавать в редактор указатель на эту структуру. Можно создавать копию этой структуры, передавать в редактор указатель на копию и переписывать обратно из копии в основную структуру только после того, как пользователь подтвердит, что он действительно в своем уме.
Непомнящий Евгений
Цитата(demiurg1978 @ Jan 10 2017, 10:11) *
Редактирование параметров в меню.


Можно сделать так, как выше предложил Сергей.

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

Функция доступа получает специальный поток (откуда читается или куда пишется значение, значение может быть разных типов, а некоторые параметры имеют несколько значений) и требуемое действие (прочитать / записать / проверить допустимость значения). Возвращает успех или код ошибки.

И далее уже имея такой массив, несложно организовать редактирование / отображение параметров через меню или по интерфейсу. Можно сделать перебор всех параметров, их сохранение в какой-то промежуточный формат и т.п.
demiurg1978
Цитата(Непомнящий Евгений @ Jan 10 2017, 14:17) *
...

Спасибо всем за ответы.Народ, не поделитесь примерами? Я всегда старался посматривать, как делают другие. Брать лучшее из примеров. Не постесняюсь сказать, что многому научился как раз на форумах. На ответах на мои вопросы и приведенных примерах.
Непомнящий Евгений
Цитата(demiurg1978 @ Jan 10 2017, 14:24) *
Спасибо всем за ответы.Народ, не поделитесь примерами? Я всегда старался посматривать, как делают другие. Брать лучшее из примеров. Не постесняюсь сказать, что многому научился как раз на форумах. На ответах на мои вопросы и приведенных примерах.


Готовый код я дать не могу - он писался для дяди, на плюсах и довольно запутан. Основная мысль, если переложить ее на Си, выглядит так

Интерфейс

CODE


enum ParamType {
paramInt,
paramUint,
paramFloat,
paramBytes,
};

// Поток для чтения и записи значений
// В простейшем случае это просто буфер, размер и тип значения в буфере
// Если параметр может иметь несколько значений, то придется усложнить (скажем сделать односвязный список таких структур)
// Если хочется иметь возможность делать произвольные действия (писать/читать сразу в формате протокола и т.п.), то тут должны быть указатели на функции
struct ParamStream {
void *buf;
uint16_t size;
ParamType type;
bool error; // произошла ошибка чтения/записи?
};

// это интерфейс к ParamStream, который позволяет читать/писать значение
// конкретная реализация зависит от вида ParamStream
uint8_t paramStream_readUI8(ParamStream *s) {
// конвертирует содержимое потока в uint8_t; если это невозможно - устанавливает s->error
}
uint16_t paramStream_readUI16(ParamStream *s);
...
uint16_t paramStream_readBytes(ParamStream *s, byte *buf, byte bufSize);

void paramStream_writeUI8(ParamStream *s, uint8_t v);
...
void paramStream_writeBytes(ParamStream *s, const byte *buf, byte bufSize);


enum ParamResult {
parres_OK,
parres_BadValue,
parres_NoParam,
parres_NoAction,
...
};

enum ParamAction {
paract_Get,
paract_Set,
paract_Check, // проверить значение на допустимость без установки - бывает полезно в меню
};

typedef ParamResult (*ParamAccessor)(ParamStream *s, ParamAction act);

struct ParamInfo {
uint16_t code;
ParamAccessor accessor;
};

/// Эта функция для пользователей данной системы. Она ищет в глобальном массиве ParamInfo параметр с данным кодом (бинарный поиск к примеру) и вызывает его accessor
ParamResult paramAccessor(uint16_t code, ParamStream *s, ParamAction act);



Использование

Код
// получить значение параметра 42
unsigned value;
ParamStream stream = {.buf=&value, .size = sizeof(value), .type=paramUint};
paramAccessor(42, &stream, paract_Get);

// установить значение параметра 55
byte buf[10] = {1, 2, 3};
ParamStream stream = {.buf=&buf, .size = sizeof(buf), .type=paramBytes};
paramAccessor(55, &stream, paract_Set);


Модуль 1

Код
int a, b;

// вычисляемый параметр только для чтения
ParamResult  param_42(ParamStream *s, ParamAction act) {
  switch(act) {
  case paract_Get: paramStream_writeUI8(s, a+b); return parres_OK;
  default: return parres_NoAction;
  }
}



Модуль 2

Код
byte buf[20];

// чтение/запись
ParamResult  param_55(ParamStream *s, ParamAction act) {
  switch(act) {
  case paract_Get: paramStream_writeBytes(s, buf, 20); return parres_OK;
  case paract_Set: paramStream_readBytes(s, buf, 20); return parres_OK;
  default: return parres_NoAction;
  }
}


Где-то нужен файл, который все функции доступа поместит в один массив (и в этот файл надо включить хидеры всех модулей с параметрами).

Код
ParamInfo paramInfo[] = {
  {42, param_42},
  {55, param_55},
  {0, 0} // терминатор


Теперь у нас есть обобщенный механизм доступа к параметру любого типа по коду. Дальше все в наших руках - можно написать адаптер для modbus / etc, доступ к произвольному параметру через меню, выплевывание всех параметров по интерфейсу и т.п.
demiurg1978
Цитата(Непомнящий Евгений @ Jan 11 2017, 12:05) *
Использование
Код
// получить значение параметра 42
unsigned value;
ParamStream stream = {.buf=&value, .size = sizeof(value), .type=paramUint};
paramAccessor(42, &stream, paract_Get);

// установить значение параметра 55
byte buf[10] = {1, 2, 3};
ParamStream stream = {.buf=&buf, .size = sizeof(buf), .type=paramBytes};
paramAccessor(55, &stream, paract_Set);

В си разве возможна такая запись: ".type" ?
scifi
Цитата(aiwa @ Jan 10 2017, 04:59) *
Само ключевое слово static появилось, при появлении второго программиста. Первый не хотел, чтобы другой использовал его глобальные переменные, вот и прятал их посредством статика.

Неправда. static позволяет не засорять глобальное пространство имён. Скажем, если в программе два десятка модулей, и в каждом - десяток своих переменных, то это уже 200 глобальных переменных. Кому нужен этот бардак?

Цитата(demiurg1978 @ Jan 11 2017, 11:25) *
В си разве возможна такая запись: ".type" ?

Погуглите на тему "что нового в C99 по сравнению с C90". Там немало приятных плюшек добавилось.
demiurg1978
Хм... Всем спасибо. Некоторым - в который уже раз... sm.gif
k155la3
Цитата(Сергей Борщ @ Jan 10 2017, 10:33) *
Собрать все параметры в структуру (наверняка это уже сделано для удобства сохранения/чтения да и вообще для повышения читабельности кода). Передавать в редактор указатель на эту структуру. Можно создавать копию этой структуры, передавать в редактор указатель на копию и переписывать обратно из копии в основную структуру только после того, как пользователь подтвердит, что он действительно в своем уме.


Тоже хотел квакнуть, но это уже сделали.
Это промежуточный вариант к ООП. Каждый модуль как-бы "псевдо-клас" со своими данными в виде струкутуры, которая extern и видна глобально через
1 указатель. Позволяет сильно сэкономить на написании "extern".
zltigo
Цитата(k155la3 @ Jan 11 2017, 11:50) *
Позволяет сильно сэкономить на написании "extern".

И не только. В большинстве случаев это помогает оптимизации, поскольку все данные с которыми работает этот программный модуль находятся гарантированно рядом, что подвигает компилятор на короткие индексные адресации. Кроме того, такие структуры данных можно паковать, если начнет место поджимать.
Сергей Борщ
QUOTE (demiurg1978 @ Jan 10 2017, 13:24) *
Народ, не поделитесь примерами?

примерно так:
CODE
bool cfg_editor::do_edit(bool from_last)
{
    // return true to edit field
    auto always            = [](cfg_cache &)     { return true; };
    auto dhcp_disabled     = [](cfg_cache & cfg) { return cfg->Localhost.DHCP_enabled == false; };


    struct
    {
        bool (*is_item_active)(cfg_cache &);
        cfg_editor_ui::result (*function)(cfg_editor *);
    } const Table[] =
    {
    #define HANDLER(edit_func, param_name, ...)  [](cfg_editor * editor) { return editor-> edit_func (param_name , editor->Cfg-> __VA_ARGS__); }
        { always,         HANDLER(edit_MAC,       "MAC address    ",                          Localhost.MAC_address) },
        { always,         HANDLER(choose_YN,      "DHCP enabled "  ,                          Localhost.DHCP_enabled) },
        { dhcp_disabled,  HANDLER(edit_IP,        "IP address     ",                          Localhost.IP_address.addr) },
        { dhcp_disabled,  HANDLER(edit_IP,        "Netmask        ",                          Localhost.Netmask.addr) },
        { dhcp_disabled,  HANDLER(edit_IP,        "Gateway        ",                          Localhost.Gateway.addr) },
#if LWIP_DNS
        { dhcp_enabled,   HANDLER(edit_IP,        "DNS1           ",                          Localhost.DNS[0].addr) },
        { dhcp_enabled,   HANDLER(edit_IP,        "DNS2           ",                          Localhost.DNS[1].addr) },
#endif
        { always,         HANDLER(edit,           "Hostname       ",                          Localhost.Hostname) },

        { always,         HANDLER(edit,           "Telnet port    ",                          Telnet.Port) },
        { always,         HANDLER(edit_password,  "Telnet password (space = disabled)",       Telnet.Password) },

        { always,         HANDLER(edit,           "Serial port baudrate   ",          Serial.Baudrate, 600, 115200) },
        .........
    };

    size_t const Last_index = sizeof(Table) / sizeof(Table[0]) - 1;
    size_t Index = from_last ? Last_index : 0;

    for(;;)
    {
        Console.new_line();
    Repeat:
        switch(Table[Index].function(this))
        {
        case result::CONSOLE_CLOSED:
        case result::TIMEOUT:
            return false;

        case result::ABORTED:
            return true;

        case result::KEY_UP:
            if(Index == 0)
            {
                Console.send('\r');
                goto Repeat;
            }
            while(--Index && !Table[Index].is_item_active(Cfg))
               ;
            break;

        case result::JUST_ENTER:
        case result::OK:
            if(Index == Last_index)
                return true;
            while(++Index < Last_index && !Table[Index].is_item_active(Cfg))
               ;

            if(!Table[Index].is_item_active(Cfg))   // if last item inactive
                return true;
            break;

        case result::KEY_DOWN:
            if(Index == Last_index)
            {
                Console.send('\r');
                goto Repeat;
            }
            while(++Index < Last_index && !Table[Index].is_item_active(Cfg))
               ;
            // last index inactive, go back to active
            if(Index == Last_index && !Table[Index].is_item_active(Cfg))
            {
                while(--Index && !Table[Index].is_item_active(Cfg))
                   ;
                Console.send('\r');
                goto Repeat;

            }
            break;
        }
    }
    return true;    // make eclipse parser happy
}

Непомнящий Евгений
Цитата(Сергей Борщ @ Jan 11 2017, 15:52) *
примерно так:
struct
{
bool (*is_item_active)(cfg_cache &);
cfg_editor_ui::result (*function)(cfg_editor *);
} const Table[]


Таблица const, но не static - живет на стеке и заполняется при каждом входе в функцию sm.gif
Сергей Борщ
QUOTE (Непомнящий Евгений @ Jan 11 2017, 15:25) *
Таблица const, но не static - живет на стеке и заполняется при каждом входе в функцию sm.gif
Оп-па! Правда со static оно тоже в ОЗУ (до стандарта C++17 лямбды не являются constexpr), но уже не на стеке. Спасибо, упустил.
aiwa
Цитата(scifi @ Jan 11 2017, 10:31) *
Неправда. static позволяет не засорять глобальное пространство имён. Скажем, если в программе два десятка модулей, и в каждом - десяток своих переменных, то это уже 200 глобальных переменных. Кому нужен этот бардак?

Ну бардак все-таки не хаос. Переменные видимы в определенном порядке и избыточное количество не критично: пусть лучше уж валяется в глобальном списке, чем тратить на нее семиразовое нажатие клавиш. И реальная необходимость возникла не из-за "засорять", а именно "спрятать" от второго. Но это уже флейм, ибо Вы обрезали главное: один программист - сам себе хозяин. И перевод из static в глобальную особого значения не имеет: насколько я понимаю имелись ввиду переменные static исключительно внутри функции.
Но чтобы следовать упомянутому ТС принципу "использование глобальных переменных нужно максимально минимизировать" перевода из static в глобальные недостаточно, нужно перевести эту переменную в параметры. Этот принцип никак не регламентирует количество глобальных переменных.

Было:
void func()
{
static int value =0;
//
модифицирующий код value;
}

Стало:
int value =0;

void func(int* value)
{
//
модифицирующий код value;
}







Сергей Борщ
QUOTE (aiwa @ Jan 12 2017, 14:39) *
Переменные видимы в определенном порядке и избыточное количество не критично: пусть лучше уж валяется в глобальном списке, чем тратить на нее семиразовое нажатие клавиш. И реальная необходимость возникла не из-за "засорять", а именно "спрятать" от второго. Но это уже флейм, ибо Вы обрезали главное: один программист - сам себе хозяин. И перевод из static в глобальную особого значения не имеет
Из соседней ветки:
QUOTE (Mister_DSP @ Jan 13 2017, 08:13) *
Создаю библиотеку (lib) в Keil по исходникам из множества файлов.
Затем получившуюся библиотеку пристыковываю к другому проекту (главному).

При линковке выдаёт ошибку: найдены одинаковые имена в libfile.o и module.o

Исходные тексты программ не моего авторства, около 100 имён совпадает, так что переименовывать не вариант.


Попробуйте убедить его, что он сам себе хозяин и static значения не имеет wink.gif
Dog Pawlowa
Цитата(Сергей Борщ @ Jan 11 2017, 15:52) *
примерно так:

У этого автора не так все легко с абстракциями, вряд ли вы ему помогли.

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

Код
#define    HALLS_CHANNELS_QTY    2

typedef struct
{    int             position;
    unsigned char    cur;                //current_inputs;
    unsigned char    prev;            //previous_inputs
    unsigned char    prev_prev;        //before previous inputs
    unsigned char    init;
}    hall_type;

extern hall_type hall[HALLS_CHANNELS_QTY];
extern hall_type old_hall[HALLS_CHANNELS_QTY];
Укушенный воблой
Цитата(demiurg1978 @ Jan 9 2017, 19:26) *
Хочу узнать мнение насчет использования глобальных переменных. Спрашиваю вот почему: неоднократно слышал, что использование глобальных переменных нужно максимально минимизировать. Откуда пошло это? В данный момент пишу проект. Свои проекты всегда стараюсь разделить на модули. static переменные. использование в других модулях посредством set_value (); get_value ();. Но в нынешнем проекте у меня много параметров. И если честно, я заколебался на каждую переменную писать свои функции установки и получения переменных. Хочу вывести переменные из static в глобальные.
Ваши за и против.

Не один из принципов нельзя возводить в абсолют.
Если нельзя, но очень хочется и это сильно упрощает жизнь - то можно.
Вон про GOTO тоже написано, что "правильные пасаны" GoTO не используют.
А тем не менее ИМЕННО GOTO в ряде случаев упрощает код и делает его более читабельным
Непомнящий Евгений
Цитата(aiwa @ Jan 12 2017, 15:39) *
Ну бардак все-таки не хаос. Переменные видимы в определенном порядке и избыточное количество не критично: пусть лучше уж валяется в глобальном списке, чем тратить на нее семиразовое нажатие клавиш.


У вас очень трепетное отношение к нажатию клавиш sm.gif Опять же, имена глобальных переменных обычно должны быть длинными и осмысленными, часто это больше 7 символов wink.gif
aiwa
Цитата(Сергей Борщ @ Jan 14 2017, 12:53) *
Попробуйте убедить его, что он сам себе хозяин и static значения не имеет wink.gif

Как раз в приведенном примере из соседней ветки static значения не имеет:
static имеет целью сделать переменную недоступной извне, что совершенно не подходит топикстартеру.
Там конфликт имен в чистом виде.

Цитата(Непомнящий Евгений @ Jan 16 2017, 07:14) *
У вас очень трепетное отношение к нажатию клавиш sm.gif Опять же, имена глобальных переменных обычно должны быть длинными и осмысленными, часто это больше 7 символов wink.gif

7-кратное нажатие - это написание "static"+пробел перед глобальными переменными, которые желательно сделать недоступными вне файла.
В случае "сам себе хозяин" без них можно обойтись.

Непомнящий Евгений
Цитата(aiwa @ Jan 16 2017, 11:26) *
7-кратное нажатие - это написание "static"+пробел перед глобальными переменными, которые желательно сделать недоступными вне файла.
В случае "сам себе хозяин" без них можно обойтись.


Я как раз про нажатия и говорю. Глобальная переменная - это тип, название и комментарий, причем название должно быть достаточно длинным. Т.е. несколько десятков символов точно. На этом фоне экономия 7 символов - это ни о чем. Можно конечно давать имена вроде xz42 и комментарии не писать maniac.gif

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

Поэтому рекомендация не использовать static из-за затрат на его написание смотрится странновато sm.gif Но если экономить символы, то ничто не мешает написать #define S static - это минус 5 нажатий wink.gif
aiwa
Цитата(Непомнящий Евгений @ Jan 16 2017, 12:14) *
Поэтому рекомендация не использовать static из-за затрат на его написание смотрится странновато sm.gif


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


Цитата(Непомнящий Евгений @ Jan 16 2017, 12:14) *
Но если экономить символы, то ничто не мешает написать #define S static - это минус 5 нажатий wink.gif

Не, такой хоккей нам не нужен.
Конечно, это дело личных предпочтений, но как по мне "S" вместо static смотрится еще хуже переменной xz42.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2024 Invision Power Services, Inc.