Внутренние статические переменные в C, вы бы их использовали? - PullRequest
22 голосов
/ 11 февраля 2009

В C вы можете иметь внешние статические переменные, которые можно просматривать везде в файле, в то время как внутренние статические переменные видны только в функции, но являются постоянными

Например:

#include <stdio.h>

void foo_bar( void )
{
        static counter = 0;
        printf("counter is %d\n", counter);
        counter++;
}
int main( void )
{
        foo_bar();
        foo_bar();
        foo_bar();
 return 0;
}

вывод будет

counter is 0
counter is 1
counter is 2

Мой вопрос: зачем вам использовать внутреннюю статическую переменную? Если вы не хотите, чтобы ваша статическая переменная была видна в остальной части файла, разве функция не должна быть в собственном файле?

Ответы [ 13 ]

36 голосов
/ 11 февраля 2009

Эта путаница обычно возникает из-за того, что ключевое слово static служит двум целям.

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

Срок действия объектов, созданных на уровне файлов, уже определен в силу того факта, что они находятся на уровне файлов. Ключевое слово static делает его невидимым для компоновщика.

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

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

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

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

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

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

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

9 голосов
/ 11 февраля 2009

Они используются для реализации таких инструментов, как strtok, и они вызывают проблемы с повторным входом ...

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

7 голосов
/ 11 февраля 2009

Например, в C ++ он используется как один из способов получения единичных значений

SingletonObject& getInstance()
{
  static SingletonObject o;
  return o;
}

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

Объявление "Разве функция не должна быть в своем собственном файле"

Конечно, нет, это чепуха. Большая часть языка программирования заключается в том, чтобы облегчить изоляцию и, следовательно, повторное использование кода (локальные переменные, процедуры, структуры и т. Д. Все это делают), и это просто еще один способ сделать это.

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

4 голосов
/ 11 февраля 2009

Я считаю это удобным для однократной, отложенной инициализации:

int GetMagic()
{
   static int magicV= -1;

   if(-1 == magicV)
   {
      //do expensive, one-time initialization
      magicV = {something here}
   }
   return magicV;
}

Как уже говорили другие, это не потокобезопасно во время самого первого вызова, но иногда вы можете сойти с рук:)

1 голос
/ 12 февраля 2009

При написании кода для микроконтроллера я бы использовал локальную статическую переменную для хранения значения подсостояния для конкретной функции. Например, если бы у меня был обработчик I2C, который вызывался каждый раз, когда main () выполнялся, тогда у него было бы свое собственное внутреннее состояние, содержащееся в статической локальной переменной. Затем каждый раз, когда он вызывался, он проверял, в каком состоянии он находился, и соответственно обрабатывал ввод / вывод (вставлял биты на выходные выводы, поднимал линию и т. Д.).

1 голос
/ 11 февраля 2009

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

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

void errorLog(...)
{
    static int reentrant = 0;
    if(reentrant)
    {
        // We somehow caused an error while logging a previous error.
        // Bail out immediately!
        hardwareReset();
    }

    // Leave ourselves a breadcrumb so we know we're already logging.
    reentrant = 1;

    // Format the error and put it in the log.
    ....

    // Error successfully logged, time to reset.
    hardwareReset();
}

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

1 голос
/ 11 февраля 2009

Больше нет. Я видел или слышал результаты локальных статических переменных функций в многопоточной среде, и это не очень красиво.

1 голос
/ 11 февраля 2009

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

Как и любая конструкция, она должна использоваться грамотно и ответственно. Вы должны знать последствия использования конструкции.

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

Например -

char *GetTempFileName()
{
  static int i;
  char *fileName = new char[1024];
  memset(fileName, 0x00, sizeof(char) * 1024);
  sprintf(fileName, "Temp%.05d.tmp\n", ++i);
  return fileName;
}

VB.NET поддерживает ту же конструкцию.

Public Function GetTempFileName() As String
  Static i As Integer = 0
  i += 1
  Return String.Format("Temp{0}", i.ToString("00000"))
End Function

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

1 голос
/ 11 февраля 2009

Некоторые варианты использования для статических переменных:

  • вы можете использовать его для счетчиков и не загрязнять глобальное пространство имен.
  • Вы можете защитить переменные, используя функцию, которая получает значение в качестве указателя и возвращает внутреннюю статическую переменную. Таким образом, вы можете контролировать, как значение присваивается. (используйте NULL, когда вы просто хотите получить значение)
1 голос
/ 11 февраля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...