2009-02-10 8 views
21

В С вы можете иметь внешние статические переменные, которые можно просмотреть каждый, где в файле, а внутренние статические переменные видны только в функцию, но является стойкимВнутренние статические переменные в 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 

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

ответ

35

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

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

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

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

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

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

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

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

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

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

+0

Этот ответ сидит некоторое время, я вижу, но это не совсем правильно. Действительно, ключевое слово 'static' выполняет множество задач в C. Однако важная вещь, которую следует различать, это _identifier_, используемый для ссылки на объект, и _storage_, используемый объектом. Ключевое слово 'static' * всегда * ограничивает видимость идентификатора текущей областью, будь то на уровне файла или уровне блока. Путаница возникает потому, что 'static' также изменяет _storage_ _duration_ объекта, когда он используется с переменной в области _block_. Обратите внимание на разницу. _identifier_ и _storage_. –

+0

Один из способов запомнить это различие - помнить, что на уровне _block_ ключевое слово 'auto' является значением по умолчанию для идентификаторов уровня _block_ _unless_, которое переопределяется явным использованием ключевого слова' static'. Я подозреваю, что если ключевое слово 'auto' всегда требовалось присутствовать, если присутствовало' static', то уровень замешательства по двойному значению 'static' был бы значительно сокращен. –

+0

@ Грег, я не уверен, что согласен с этим, хотя это всегда возможно, я неправильно понял ваши намерения. 'static' имеет нулевой эффект на видимость идентификатора в _block._ Это тот факт, что он находится в блоке, который ограничивает его видимость для этого блока, а' static' на самом деле не меняет этого. Внедрение переменной уровня блока в статические изменения _only_ длительность его хранения. – paxdiablo

1

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

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

+1

:-) Большинство ненавидимых? Я думаю, вы найдете setjmp(), и longjmp() может сражаться за эту честь. Сортировка на стероидах. – paxdiablo

+0

И получает() - если только вы sipmly не считаете это вообще ..., после чего setjmp() и strtok() будут сражаться с тем, кто хуже. –

+0

LOL, это был самый ненавистный для моего босса пара рабочих дней назад. ;-) –

9

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

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

+0

У вас есть ссылки для дальнейшего чтения о таких проблемах? Благодаря! – yungchin

+0

Не совсем. Я понял, что эти звери осмосом. Я думаю, что реализация strtok поучительна, как и функция strtok_r. Обычно предпочтительнее более поздний patter ... – dmckee

+0

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

0

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

1

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

7

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

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

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

объявления «не должна быть функция в отдельном файле»

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

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

+0

I не проголосует, но я вижу две проблемы - (1) Вопрос был о C. (2) Статика уровня файла - это глобалы только по продолжительности, а не по видимости, поэтому они, по крайней мере, немного лучше. – paxdiablo

+0

(1) Да, но я думаю, что на C++ они более полезны, чем на C, поэтому лучше продемонстрировать (2) немного лучше, но все проблемы с реентерацией и потоками являются общими для одиночных, статических и глобальных данных. – jpalecek

1

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

1

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

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

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

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

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

Например -

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

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

+0

Не могли бы вы немного расширить этот момент? Спасибо – hhafez

+0

Есть способы и средства для обеспечения этой работоспособности без ущерба для инкапсуляции. Просмотрите «данные, зависящие от потока» или см. Http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/rzahw/rzahwdatco.htm для примера. – paxdiablo

+0

Oy, нить-локальное хранилище не намного лучше. Необязательная магия компилятора для поддержки TLS - это не совсем моя чашка чая. – MSN

4

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

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

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

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

+0

Я вполне согласен. Я видел много, много многопроцессорных процедур, где я мог доказать, что первый вызов был потокобезопасным. – Joshua

+1

И если вы компилируете что-то вроде -D_THREADSAFE, код будет иметь защиту вокруг оператора if, или было бы требованием вызвать GetMagic в основном потоке, прежде чем запускать другие потоки. Другими словами, есть проблема вокруг проблемы, которая не нарушает инкапсуляцию. – paxdiablo

0

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

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

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(); 
} 

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

+0

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

+0

Да, это не совсем то, что я намеревался, поэтому я разъяснил это. –

1

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

 Смежные вопросы

  • Нет связанных вопросов^_^