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

Я пишу встроенную прошивку и иногда трудно решить, когда мне нужно volatile или нет.

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

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

5 ответов


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


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

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

Так что если у вас есть это:

static int x=0;

int func (void)
{
  if(x == 0)
  {
    return 1;
  }
  else
  {
    return 0;
  }
}

interrupt void isr (void)
{
  x = SOMETHING;
}

тогда компилятор подумает: "Хм, x нигде не изменяется, так как "isr" никогда не изменяется звонили из программы. Так что x всегда 0, я оптимизирую код до этого:"

int func (void)
{
  return 1;
}

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

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


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

по существу, если это аппаратное обеспечение сопоставлено или может быть изменено через прерывание (от аппаратного обеспечения), отметьте его изменчивым.


помимо маркировки переменной volatile для принудительной загрузки (как предлагает @dasblinkenlight), вы также должны предпринять шаги, чтобы гарантировать, что переменная читается (и записывается) атомарно. На некоторых платформах для объектов определенного размера (например, 32-разрядные значения на последних процессорах x86) это происходит автоматически. В общем случае вам может потребоваться поместить блокировку синхронизации вокруг переменной, например мьютекс или семафор. Это относительно легко сделать, когда асинхронный код является потоком. Я не конечно, что делать, когда есть истинное прерывание, поскольку некоторые методы синхронизации могут быть невозможны. Документация вашей платформы должна дать некоторое представление здесь.


вся общая память должна быть объявлена volatile.

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