Являются ли глобальные переменные плохими?
В C/C++ глобальные переменные настолько плохи, насколько их считает мой профессор?
28 ответов
Проблема с глобальными переменными состоит в том, что, поскольку каждая функция имеет к ним доступ, становится все труднее выяснить, какие функции действительно читают и записывают эти переменные.
Чтобы понять, как работает приложение, вам нужно учитывать каждую функцию, которая изменяет глобальное состояние. Это можно сделать, но по мере роста приложения оно будет становиться все труднее и практически невозможно (или, по крайней мере, будет пустой тратой времени).
Если вы не полагаетесь на глобальные переменные, вы можете при необходимости передавать состояние между различными функциями. Таким образом, у вас гораздо больше шансов понять, что делает каждая функция, поскольку вам не нужно принимать во внимание глобальное состояние.
Важно помнить общую цель: ясность
Существует правило "без глобальных переменных", потому что большую часть времени глобальные переменные делают смысл кода менее понятным.
Однако, как и многие правила, люди помнят правило, а не то, для чего предназначалось правило.
Я видел программы, которые, кажется, удваивают размер кода, передавая огромное количество параметров, просто чтобы избежать зла глобальных переменных. В конце концов, использование глобалов сделало бы программу более понятной для тех, кто ее читает. Бездумно придерживаясь слова правила, оригинальный программист потерпел неудачу в намерении правила.
Так что да, глобалы часто плохие. Но если вы чувствуете, что в конечном итоге цель программиста становится более понятной благодаря использованию глобальных переменных, то продолжайте. Однако помните о снижении ясности, которое происходит автоматически, когда вы заставляете кого-то обращаться ко второму коду (глобальным), чтобы понять, как работает первый фрагмент.
Мой профессор говорил что-то вроде: использование глобальных переменных - это нормально, если вы используете их правильно. Я не думаю, что когда-либо у меня получалось правильно их использовать, поэтому я вообще редко их использовал.
Проблема, которую создают глобальные переменные для программиста, состоит в том, что он расширяет поверхность межкомпонентной связи между различными компонентами, которые используют глобальные переменные. Это означает, что по мере увеличения числа компонентов, использующих глобальную переменную, сложность взаимодействий также может увеличиваться. Такое усиленное сцепление, как правило, облегчает ввод дефектов в систему при внесении изменений, а также затрудняет диагностику и исправление дефектов. Это увеличение связи может также уменьшить количество доступных опций при внесении изменений и может увеличить усилие, требуемое для изменений, так как часто необходимо проследить через различные модули, которые также используют глобальную переменную, чтобы определить последствия изменений.
Цель инкапсуляции, которая в основном противоположна использованию глобальных переменных, состоит в том, чтобы уменьшить связь, чтобы сделать понимание и изменение источника более простым, безопасным и более простым для тестирования. Намного проще использовать модульное тестирование, когда глобальные переменные не используются.
Например, если у вас есть простая глобальная целочисленная переменная, которая используется в качестве перечислимого индикатора, который различные компоненты используют в качестве конечного автомата, и вы затем вносите изменения, добавляя новое состояние для нового компонента, вы должны затем проследить все остальные компоненты, чтобы гарантировать, что изменение не повлияет на них. Примером возможной проблемы может быть, если switch
оператор для проверки значения глобальной переменной перечисления с case
операторы для каждого из текущих значений используются в разных местах, и так случилось, что некоторые из switch
заявления не имеют default
В случае, если вы обрабатываете неожиданное значение для глобального значения, у вас вдруг возникает неопределенное поведение в отношении приложения.
С другой стороны, использование общей области данных может использоваться для хранения набора глобальных параметров, на которые ссылаются в приложении. Этот подход часто используется со встроенными приложениями с небольшим объемом памяти.
При использовании глобальных переменных в таких приложениях обычно ответственность за запись в область данных распределяется на один компонент, а все остальные компоненты видят эту область как const
и читать из него, никогда не писать в него. Использование этого подхода ограничивает проблемы, которые могут возникнуть.
Вот несколько проблем с использованием глобальных переменных, которые нужно обойти.
Когда источник для глобальной переменной, такой как структура, изменяется, все, что его использует, должно быть перекомпилировано, чтобы все, кто использует переменную, знали свой истинный размер и шаблон памяти.
Если более чем один компонент может изменять глобальную переменную, вы можете столкнуться с проблемами, связанными с несовместимыми данными в глобальной переменной. В многопоточном приложении вам, вероятно, потребуется добавить какую-то блокировку или критическую область, чтобы обеспечить способ, чтобы только один поток за раз мог изменять глобальную переменную, а когда поток изменяет переменную, все изменения завершены и фиксируется до того, как другие потоки смогут запросить переменную или изменить ее.
Отладка многопоточного приложения, использующего глобальную переменную, может оказаться более сложной задачей. Вы можете столкнуться с условиями гонки, которые могут создать дефекты, которые трудно воспроизвести. С несколькими компонентами, связывающимися через глобальную переменную, особенно в многопоточном приложении, очень трудно понять, какой компонент изменяет переменную, когда и как она меняет переменную.
Конфликт имен может быть проблемой с использованием глобальных переменных. Локальная переменная, имя которой совпадает с именем глобальной переменной, может скрывать глобальную переменную. Вы также сталкиваетесь с проблемой соглашения об именах при использовании языка программирования C. Обходной путь - разделить систему на подсистемы с глобальными переменными для конкретной подсистемы, начинающимися с тех же первых трех букв (см. Это при разрешении коллизий пространства имен в задаче C). C++ предоставляет пространства имен, а с C вы можете обойти эту проблему, создав глобально видимую структуру, членами которой являются различные элементы данных и указатели на данные и функции, которые представлены в файле как статические, следовательно, с видимостью файла, так что на них можно ссылаться только через глобально видимая структура.
В некоторых случаях первоначальное намерение приложения изменяется так, что глобальные переменные, которые предоставляли состояние для одного потока, модифицируются, чтобы позволить запускаться нескольким дублирующимся потокам. Примером может служить простое приложение, предназначенное для одного пользователя, использующее глобальные переменные для состояния, а затем из управления поступает запрос на добавление интерфейса REST, позволяющего удаленным приложениям выступать в качестве виртуальных пользователей. Итак, теперь вы столкнулись с необходимостью дублировать глобальные переменные и их информацию о состоянии, чтобы у отдельного пользователя, а также у каждого виртуального пользователя из удаленных приложений был свой собственный уникальный набор глобальных переменных.
Глобальные переменные следует использовать только тогда, когда у вас нет альтернативы. И да, это включает в себя синглтоны. В 90% случаев глобальные переменные вводятся для экономии затрат на передачу параметров. И тогда происходит многопоточность / модульное тестирование / обслуживание кода, и у вас есть проблема.
Так что да, в 90% случаев глобальные переменные плохие. Исключения вряд ли будут замечены вами в ваши студенческие годы. Единственное исключение, которое я могу придумать, - это работа с глобальными объектами, такими как таблицы прерываний. Такие вещи, как соединение с БД, кажутся глобальными, но это не так.
Глобальные переменные так же плохи, как вы их делаете, не меньше.
Если вы создаете полностью инкапсулированную программу, вы можете использовать глобальные переменные. Использовать глобальные перемены - это "грех", но грехи программирования носят философский характер.
Если вы посмотрите L.in.oleum, вы увидите язык, переменные которого являются исключительно глобальными. Это не масштабируется, потому что у всех библиотек нет другого выбора, кроме как использовать глобальные переменные.
Тем не менее, если у вас есть выбор и вы можете игнорировать философию программиста, глобальные переменные не так уж и плохи.
Как и Готос, если вы используете их правильно.
Большая "плохая" проблема заключается в том, что, если вы используете их неправильно, люди кричат, разбивается судно на Марсе, и мир взрывается… или что-то в этом роде.
Да, но вы не несете затрат на глобальные переменные, пока не перестанете работать в коде, который использует глобальные переменные, и не начнете писать что-то еще, которое использует код, который использует глобальные переменные. Но стоимость все еще там.
Другими словами, это долгосрочные косвенные затраты, и поэтому большинство людей считают, что это неплохо.
Если возможно, что ваш код подвергнется интенсивной проверке в ходе судебного разбирательства в Верховном суде, тогда вы должны избегать глобальных переменных.
Смотрите эту статью: Багги Алкотестер код отражает важность исходного обзора
Были некоторые проблемы со стилем кода, которые были определены в обоих исследованиях. Одной из стилистических проблем, которые волновали рецензентов, было широкое использование незащищенных глобальных переменных. Это считается плохой формой, поскольку увеличивает риск того, что состояние программы станет несовместимым или что значения будут случайно изменены или перезаписаны. Исследователи также выразили некоторую обеспокоенность по поводу того факта, что десятичная точность не поддерживается последовательно во всем коде.
Бьюсь об заклад, те разработчики хотят, чтобы они не использовали глобальные переменные!
Я бы ответил на этот вопрос другим вопросом: используете ли вы синглтоны/ плохо ли синглтоны?
Потому что (почти все) использование сингелтона является прославленной глобальной переменной.
Проблема не в том, что они плохие, а в том, что они опасны. У них есть свои плюсы и минусы, и бывают ситуации, когда они являются либо наиболее эффективным, либо единственным способом решения конкретной задачи. Тем не менее, ими очень легко злоупотреблять, даже если вы предпринимаете шаги, чтобы всегда их правильно использовать.
Несколько плюсов:
- Можно получить доступ из любой функции.
- Можно получить доступ из нескольких потоков.
- Никогда не выйдет за рамки, пока программа не закончится.
Несколько минусов:
- Доступ к ним возможен из любой функции, без необходимости явного перетаскивания в качестве параметра и / или документирования.
- Не потокобезопасен.
- Загрязняет глобальное пространство имен и потенциально вызывает конфликты имен, если не приняты меры для предотвращения этого.
Заметьте, если хотите, что первые два плюса и первые два минуса, которые я перечислил, - это одно и то же, только с другой формулировкой. Это связано с тем, что возможности глобальной переменной действительно могут быть полезны, но именно те функции, которые делают их полезными, являются источником всех их проблем.
Несколько потенциальных решений некоторых проблем:
- Подумайте, действительно ли они являются лучшим или наиболее эффективным решением проблемы. Если есть какие-то лучшие решения, используйте это вместо этого.
- Поместите их в пространство имен [C++] или одноэлементную структуру [C, C++] с уникальным именем (хорошим примером будет
Globals
или жеGlobalVars
) или использовать стандартное соглашение об именах для глобальных переменных (например,global_[name]
или жеg_module_varNameStyle
(как отмечено в комментариях underscore_d)). Это будет как документировать их использование (вы можете найти код, который использует глобальные переменные, выполняя поиск пространства имен / структуры), так и минимизировать влияние на глобальное пространство имен. - Для любой функции, которая обращается к глобальным переменным, явно задокументируйте, какие переменные она читает и какие пишет. Это облегчит поиск неисправностей.
- Поместите их в свой исходный файл и объявите их
extern
в связанном заголовке, поэтому их использование может быть ограничено модулями компиляции, которым необходим доступ к ним. Если ваш код основан на большом количестве глобальных переменных, но для каждого модуля компиляции требуется доступ только к нескольким из них, вы можете рассмотреть возможность сортировки их по нескольким исходным файлам, чтобы было проще ограничить доступ каждого файла к глобальным переменным. - Настройте механизм их блокировки и разблокировки и / или спроектируйте свой код так, чтобы как можно меньше функций могло реально изменять глобальные переменные. Чтение их намного безопаснее, чем их написание, хотя в многопоточных программах могут возникать проблемы с потоками.
- По сути, минимизируйте доступ к ним и максимизируйте уникальность имени. Вы хотите избежать коллизий имен и иметь как можно меньше функций, которые потенциально могут изменить любую переменную.
Хорошие они или плохие, зависит от того, как вы их используете. Большинство склонны использовать их плохо, отсюда и общая настороженность по отношению к ним. При правильном использовании они могут быть главным благом; однако при плохом использовании они могут вернуться и укусить вас, когда и как вы меньше всего этого ожидаете.
Хороший способ взглянуть на это - это то, что они сами неплохие, но они допускают плохой дизайн и могут экспоненциально умножать эффекты плохого дизайна.
Даже если вы не собираетесь их использовать, лучше знать, как их использовать безопасно, и не использовать их, чем не использовать их, потому что вы не знаете, как их использовать безопасно. Если вы когда-нибудь окажетесь в ситуации, когда вам нужно поддерживать уже существующий код, который опирается на глобальные переменные, у вас могут возникнуть трудности, если вы не знаете, как их правильно использовать.
Как кто-то сказал (я перефразирую) в другой теме: "Подобные правила не должны нарушаться, пока вы полностью не поймете последствия этого".
Бывают ситуации, когда необходимы глобальные переменные или, по крайней мере, они очень полезны (например, работа с системными обратными вызовами). С другой стороны, они также очень опасны по всем причинам, которые вам сказали.
Существует много аспектов программирования, которые, вероятно, следует оставить экспертам. Иногда вам нужен очень острый нож. Но вы не можете использовать один, пока не будете готовы...
Использование глобальных переменных - это как грязь под ковриком. Это быстрое решение, и в краткосрочной перспективе намного проще, чем получить пылесборник или пылесос для его очистки. Однако, если вы когда-нибудь в конечном итоге будете двигать коврик, у вас будет большой сюрприз под ним.
Глобальные переменные обычно плохие, особенно если другие люди работают с тем же кодом и не хотят тратить 20 минут на поиск всех мест, на которые ссылается переменная. А добавление потоков, которые изменяют переменные, приносит совершенно новый уровень головной боли.
Глобальные константы в анонимном пространстве имен, используемые в одном модуле перевода, хороши и распространены в профессиональных приложениях и библиотеках. Но если данные являются изменяемыми, и / или они должны быть разделены между несколькими TU, вы можете захотеть инкапсулировать их - если не ради дизайна, то ради кого-либо отладки или работы с вашим кодом.
Я думаю, что ваш профессор пытается избавиться от вредной привычки еще до того, как она начнется.
Глобальные переменные имеют свое место, и, как говорили многие люди, знание, где и когда их использовать, может быть сложным. Так что я думаю, а не вдаваться в подробности о том, почему, как, когда и где глобальные переменные ваш профессор решил просто запретить. Кто знает, он может отменить их в будущем.
Глобальные переменные плохи, если они позволяют вам манипулировать аспектами программы, которые должны быть изменены только локально. В ООП глобалы часто конфликтуют с идеей инкапсуляции.
Точно нет. Хотя злоупотреблять ими... это плохо.
Бессмысленно удалять их ради всего лишь... бездумно. Если вы не знаете о преимуществах и недостатках, лучше держаться подальше и действовать так, как вас учили / изучали, но в глобальных переменных нет ничего неявно неправильного. Когда вы понимаете плюсы и минусы, лучше примите собственное решение.
Я хотел бы возразить против того, что в этой теме делается вывод о том, что многопоточность усложняется или невозможна сама по себе. Глобальные переменные являются общим состоянием, но альтернативы глобальным переменным (например, передача указателей) также могут иметь общее состояние. Проблема с многопоточностью заключается в том, как правильно использовать общее состояние, а не в том, является ли это состояние общим через глобальную переменную или что-то еще.
Большую часть времени, когда вы делаете многопоточность, вам нужно поделиться чем-то. Например, в шаблоне "производитель-потребитель" вы можете использовать некоторую потокобезопасную очередь, содержащую рабочие блоки. И вам разрешено делиться этим, потому что эта структура данных является поточно-ориентированной. Является ли эта очередь глобальной или нет, совершенно не имеет значения, когда речь заходит о безопасности потоков.
Подразумеваемая надежда, выраженная в этом потоке, что преобразование программы из однопоточной в многопоточную будет проще, если не использовать глобальные переменные, наивно. Да, глобалы помогают стрелять себе в ногу, но есть много способов выстрелить себе.
Я не защищаю глобальные переменные, так как другие пункты все еще остаются в силе, моя точка зрения заключается просто в том, что количество потоков в программе не имеет ничего общего с переменной областью действия.
Нет, они совсем не плохие. Вам нужно взглянуть на (машинный) код, сгенерированный компилятором, чтобы сделать это определение, иногда гораздо хуже использовать локальный, чем глобальный. Также обратите внимание, что добавление "static" в локальную переменную делает ее глобальной (и создает другие уродливые проблемы, которые реальный глобал может решить). "местные глобалы" особенно плохи.
Глобальные переменные дают вам полный контроль над использованием памяти, что гораздо сложнее сделать с местными жителями. В наши дни это имеет значение только во встроенных средах, где память довольно ограничена. Что-то, что нужно знать, прежде чем предполагать, что встроенные - то же самое, что и в других средах, и что правила программирования одинаковы для всех.
Хорошо, что вы подвергаете сомнению правила, которым вас учат, большинство из них не по тем причинам, о которых вам говорят. Но самый важный урок заключается не в том, что это правило, которое нужно носить с собой вечно, а в том, что это правило необходимо соблюдать, чтобы пройти этот класс и двигаться вперед. В жизни вы обнаружите, что для компании XYZ у вас будут другие правила программирования, которые вам, в конце концов, придется соблюдать, чтобы продолжать получать зарплату. В обеих ситуациях вы можете спорить о правиле, но я думаю, что вам повезет больше на работе, чем в школе. Вы просто еще один из многих студентов, ваше место скоро будет заменено, профессора не будут, на работе вы являетесь одной из небольшой команды игроков, которые должны довести этот продукт до конца, и в этой среде разработанные правила предназначены для выгодно членам команды, а также продукту и компании, поэтому, если у всех есть единомышленники или если для конкретного продукта есть веские технические причины нарушать то, чему вы научились в колледже или какую-то книгу по универсальному программированию, то продайте свою идею команда и запишите его как действительный, если не предпочтительный метод. Все в честной игре в реальном мире.
Если вы будете следовать всем правилам программирования, которым вас учат в школе или книгах, ваша карьера программиста будет крайне ограничена. Вероятно, вы сможете выжить и иметь плодотворную карьеру, но широта и ширина доступных вам сред будут крайне ограничены. Если вы знаете, как и почему существует правило и можете его защитить, это хорошо, если вы только рассуждаете "потому что так сказал мой учитель", ну, это не так хорошо.
Обратите внимание, что подобные темы часто обсуждаются на рабочем месте и будут продолжать развиваться по мере развития компиляторов и процессоров (и языков), так же как и правила такого рода, без защиты вашей позиции и, возможно, преподавания урока кем-то, кто придерживается другого мнения. двигаться вперед.
А пока просто делайте то, что говорит тот, кто говорит громче всего или несет самую большую палку (до тех пор, пока вы не будете кричать громко и нести самую большую палку).
Да, потому что, если вы позволите некомпетентным программистам использовать их (читайте 90%, особенно ученых), вы получите более 600 глобальных переменных, распределенных по 20+ файлам, и проект из 12 000 строк, в котором 80% функций теряют силу, возвращают пустоту и работают полностью в глобальном состоянии.
Быстро становится невозможно понять, что происходит в любой момент, если вы не знаете весь проект.
Глобальные переменные хороши в небольших программах, но ужасны, если используются в больших программах тем же способом.
Это означает, что вы можете легко привыкнуть использовать их во время обучения. Это то, от чего ваш профессор пытается вас защитить.
Когда вы станете более опытным, вам будет легче учиться, когда они в порядке.
Глобальные хороши, когда дело доходит до конфигурации. Когда мы хотим, чтобы наша конфигурация / изменения имели глобальное влияние на весь проект.
Таким образом, мы можем изменить одну конфигурацию, и изменения будут направлены на весь проект. Но я должен предупредить, что вы должны быть очень умными, чтобы использовать глобальные переменные.
Использование глобальных переменных на самом деле зависит от требований. Его преимущество заключается в том, что он уменьшает накладные расходы на повторную передачу значений.
Но ваш профессор прав, потому что это поднимает вопросы безопасности, поэтому следует как можно больше избегать использования глобальных переменных. Глобальные переменные также создают проблемы, которые иногда трудно отладить.
Например:-
Ситуации, когда значения переменных изменяются во время выполнения. В этот момент трудно определить, какая часть кода модифицирует его и на каких условиях.
В конце концов, ваша программа или приложение все еще могут работать, но это вопрос аккуратности и полного понимания происходящего. Если вы разделяете значение переменной среди всех функций, может оказаться трудным отследить, какая функция меняет значение (если функция делает это) и усложняет отладку в миллион раз
Рано или поздно вам придется изменить то, как установлена эта переменная или что происходит, когда к ней обращаются, или вам просто нужно выследить, где она была изменена.
Практически всегда лучше не иметь глобальных переменных. Просто напишите методы получения и установки плотины и будьте готовы помочь вам, когда они понадобятся вам через день, неделю или месяц.
В веб-приложениях внутри предприятия можно использовать для хранения данных сессии / окна / потока / пользователя на сервере по причинам оптимизации и для защиты от потери работы, когда соединение нестабильно. Как уже упоминалось, условия гонки должны быть обработаны. Мы используем один экземпляр класса для этой информации, и он тщательно управляется.
Я обычно использую глобальные переменные для значений, которые редко меняются, например, синглтоны или указатели на функции в динамически загружаемой библиотеке. Использование изменяемых глобальных переменных в многопоточных приложениях приводит к трудным отслеживанию ошибок, поэтому я стараюсь избегать этого как общего правила.
Использование глобального вместо передачи аргумента часто быстрее, но если вы пишете многопоточное приложение, что вы часто делаете в настоящее время, оно обычно работает не очень хорошо (вы можете использовать статику потоков, но тогда выигрыш в производительности сомнителен),
В многопоточном приложении вместо локальных переменных используйте локальные переменные, чтобы избежать состояния гонки.
Состояние гонки возникает, когда несколько потоков обращаются к общему ресурсу, по крайней мере, один поток имеет доступ на запись к данным. Тогда результат программы не предсказуем и зависит от порядка доступа к данным различными потоками.
Подробнее об этом здесь, https://software.intel.com/en-us/articles/use-intel-parallel-inspector-to-find-race-conditions-in-openmp-based-multithreaded-code
Безопасность меньше, значит любой может манипулировать переменными, если они объявлены глобальными, для объяснения этого возьмем этот пример, если у вас есть баланс в качестве глобальной переменной в вашей банковской программе, пользовательская функция может манипулировать этим, а также сотрудник банка может также манипулировать В этом и заключается проблема. Только пользователю должна быть предоставлена функция "только чтение" и "снятие", но клерк банка может добавить сумму, когда пользователь лично отдает деньги на столе. Так оно и работает.