Вы бы использовали внутренние статические переменные в C?

В 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

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


person hhafez    schedule 10.02.2009    source источник


Ответы (13)


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

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

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

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

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

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

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

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

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

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

person paxdiablo    schedule 11.02.2009
comment
Я вижу, что этот ответ какое-то время сидит без дела, но он не совсем правильный. Действительно, ключевое слово static действительно служит нескольким целям в C. Однако важно различать идентификатор, используемый для ссылки на объект, и хранилище, используемое объектом. Ключевое слово static always ограничивает видимость идентификатора текущей областью, будь то на уровне файла или на уровне блока. Путаница возникает из-за того, что static также изменяет хранилище продолжительность объекта, когда он используется с переменной в области block. Обратите внимание на разницу. идентификатора и хранилища. - person Greg A. Woods; 12.12.2012
comment
Один из способов запомнить это различие - это помнить, что на уровне block ключевое слово auto является значением по умолчанию для идентификаторов уровня block , если не переопределено явным образом используя ключевое слово static. Я подозреваю, что если бы ключевое слово auto всегда присутствовало, если не было static, то уровень путаницы по поводу двойного значения static был бы намного меньше. - person Greg A. Woods; 12.12.2012
comment
@Greg, я не уверен, что полностью согласен с этим, хотя всегда возможно, что я неправильно понял ваши намерения. static не влияет на видимость идентификатора внутри блока. Тот факт, что он находится в блоке, ограничивает его видимость этим блоком, и static фактически не меняет этого. Превращение переменной уровня блока в статическую изменяет только срок ее хранения. - person paxdiablo; 12.12.2012
comment
Что ж, строго говоря, вы правы, но, как я уже сказал, эти строгие правила вызывают путаницу. Если вы думаете о static как о всегда ограничивающей области, тогда вы охватываете случай переменной с внутренней связью (область на уровне файла), а также переменной без привязки. Я думаю, что для переменных уровня блока легче подумать о static замене или удалении неявных auto и, следовательно, также изменении продолжительности их хранения. Строгие правила необходимы только для более подробного понимания, если вы пишете компилятор C, но не если вы просто используете язык. - person Greg A. Woods; 13.12.2012
comment
Т.е. думать о static как об ограничении области действия идентификатора, который уже имеет ограниченную область действия, в целом безвредно, и это помогает унифицировать концептуальное применение static для идентификаторов с областью действия файла (которые относятся к объектам, которые уже неявно имеют статическую продолжительность хранения, и где явное использование static только преобразует их из внешней связи во внутреннюю). - person Greg A. Woods; 13.12.2012
comment
Возможно, мне не следовало говорить не совсем правильно, а скорее что-то вроде не очень полезного в контексте исходного вопроса. - person Greg A. Woods; 13.12.2012

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

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

person dmckee --- ex-moderator kitten    schedule 10.02.2009
comment
Есть ли у вас ссылки для дальнейшего чтения о таких проблемах? Спасибо! - person yungchin; 11.02.2009
comment
Не совсем. Я пришел к пониманию этих чудовищ с помощью осмоса. Я думаю, что реализация strtok поучительна, как и strtok_r. Обычно более поздний вариант предпочтительнее ... - person dmckee --- ex-moderator kitten; 11.02.2009
comment
Проблема была бы в том, если бы вы, например. если вы использовали strtok для разделения в: и вызвали другую функцию, которая сама вызывает strtok для токенов (возможно, потому, что ей нужно будет разделить какой-то токен в) - person jpalecek; 11.02.2009
comment
Спасибо, я посмотрел на strtok [здесь] [1] - даже не видя кода, просто спецификация использования говорит мне, что это ужасный дизайн! Я не уверен, означает ли это, что все случаи использования локальных статических переменных плохи ... [1]: cplusplus.com/reference/clibrary/cstring/strtok.html - person yungchin; 11.02.2009
comment
В частности, с strtok проблема в том, что существует статический указатель на строку, над которой выполняется работа. При желании можно безопасно менять разделители, но небезопасно чередовать вызовы, включающие две разные строки. Теперь попробуйте гарантировать, что при запуске нескольких потоков ... Кошмар блокировки. - person dmckee --- ex-moderator kitten; 11.02.2009
comment
Дизайн strtok как таковой не ужасен. Просто минималистичный и хрупкий. Это старый инструмент. Он делает одну простую вещь. - person dmckee --- ex-moderator kitten; 11.02.2009
comment
strtok () не плох, потому что он использует static, но потому, что он изменяет строку, которую вы ему передаете. Довольно легко реализовать strtok () с данными, специфичными для потока, которые являются потокобезопасными. - person paxdiablo; 11.02.2009
comment
потокобезопасный strtok () все равно не сработает в коде вроде ... for (token = strtok (line, :); token! = NULL; token = strtok (NULL, :)) / * разделить строку на токены / { for (pathcomp = strtok (token, /); pathcomp! = NULL; pathcomp = strtok (NULL, /)) / анализировать путь в некоторых токенах, возможно, в другой функции * / - person jpalecek; 11.02.2009

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

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

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

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

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

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

person jpalecek    schedule 10.02.2009
comment
Я не буду голосовать за вас, но вижу две проблемы: (1) Вопрос был для C. (2) Статика на уровне файлов глобальна только по продолжительности, а не по видимости, поэтому они, по крайней мере, немного лучше. - person paxdiablo; 11.02.2009
comment
(1) Да, но я думаю, что в C ++ они более полезны, чем в C, поэтому лучше продемонстрировать (2) немного лучше, но все проблемы с повторным входом и потоками являются общими для синглтонов, статических и глобальных данных. - person jpalecek; 11.02.2009

Удобно для одноразовой отложенной инициализации:

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

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

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

person DougN    schedule 11.02.2009
comment
Я вполне согласен. Я видел много-много многопоточных подпрограмм, в которых я мог доказать, что первый вызов был потокобезопасным. - person Joshua; 11.02.2009
comment
И если вы компилируете что-то вроде -D_THREADSAFE, код будет иметь защиту вокруг оператора if, или будет требоваться вызов GetMagic в основном потоке перед запуском других потоков. Другими словами, есть способ обойти проблему, не нарушая инкапсуляцию. - person paxdiablo; 11.02.2009

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

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

person Dave Markle    schedule 10.02.2009
comment
:-) Больше всего ненавидят? Думаю, вы обнаружите, что setjmp () и longjmp () борются за эту честь. Что-то вроде goto на стероидах. - person paxdiablo; 11.02.2009
comment
И gets () - если только вы не посчитаете его вообще ... после чего setjmp () и strtok () будут бороться за то, кто хуже. - person Jonathan Leffler; 11.02.2009
comment
LOL, пару вакансий назад это было больше всего ненависти для моего босса. ;-) - person Dave Markle; 11.02.2009
comment
Локальное постоянное хранение в функциях - не обязательно плохая идея. Локальное постоянное хранилище в библиотечных функциях есть. - person David Thornley; 12.02.2009
comment
@DavidThornley Вы имеете в виду, что если локальное постоянное хранилище в библиотечной функции используется двумя разными потоками, это создаст проблему? Я просто уточняю свое понимание. - person duslabo; 27.10.2013

Наверное, не очень полезны в C, но они используются в C ++, чтобы гарантировать инициализацию статики в области имен. И в C, и в C ++ есть проблемы с их использованием в многопоточных приложениях.

person Community    schedule 10.02.2009

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

person gbarry    schedule 10.02.2009

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

  • вы можете использовать его для счетчиков, и вы не загрязните глобальное пространство имен.
  • вы можете защитить переменные, используя функцию, которая получает значение в виде указателя и возвращает внутреннюю статику. Таким образом, вы можете контролировать присвоение значения. (используйте NULL, если вы просто хотите получить значение)
person George    schedule 10.02.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

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

person user62572    schedule 11.02.2009

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

person MSN    schedule 11.02.2009
comment
Есть способы и средства сделать это работоспособным без ущерба для инкапсуляции. Найдите данные по потоку или просмотрите publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/ для примеров. - person paxdiablo; 11.02.2009
comment
Ой, локальное хранилище потоков не намного лучше. Дополнительная неотслеживаемая магия компилятора для поддержки TLS - не совсем моя чашка чая. - person MSN; 11.02.2009
comment
Или если вы сделаете функцию атомарной (или как там это слово), чтобы она не позволяла другим потокам использовать ее, когда один поток ее выполняет. Еще один пример того, как изменчивое состояние - это головная боль в многопоточном программировании. - person David Thornley; 12.02.2009

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

person Stephen Friederichs    schedule 12.02.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();
}

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

person Casey Barker    schedule 11.02.2009
comment
Возможно, вы захотите пояснить свое утверждение о том, что вся статика глобальна. Это, строго говоря, неправда. - person Mark Bessey; 24.10.2009
comment
Да, это было не совсем то, что я имел в виду, поэтому я прояснил это. - person Casey Barker; 03.11.2009

Простое использование этого состоит в том, что функция может знать, сколько раз она была вызвана.

person lillq    schedule 10.02.2009