Правильное использование изменчивого ключевого слова в C - PullRequest
0 голосов
/ 11 сентября 2018

Я новичок в C, и я наткнулся на следующий код:

#include "stdio.h"

unsigned int ReturnSquare(void);

int main(void) {

int k;
int *mPtr;

mPtr = (int*) 0x1234;

*mPtr = 10;

 k = (int) ReturnSquare();

 printf("%p --> %d\n",mPtr,k);

}

unsigned int ReturnSquare(void)
{
  unsigned  volatile int a = * (unsigned volatile int *) 0x1234;
  unsigned  volatile int b = * (unsigned volatile int *) 0x1234;
  return a * b;
}

Не могли бы вы помочь мне понять, как используется volatile в этом коде?

ЭтоКажется, что программа работает неправильно.Любые предложения и объяснения очень приветствуются.Заранее спасибо.

Ответы [ 3 ]

0 голосов
/ 11 сентября 2018

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

0 голосов
/ 11 сентября 2018

Код, который вы показываете, является плохим примером того, что делает volatile, и плохим примером для кода C в целом.

Сначала код выполняет следующее:

mPtr = (int*) 0x1234;
*mPtr = 10;

он принимает, казалось бы, произвольный адрес 0x1234 и помещает туда значение int.Как правило, вы не можете знать, что вам разрешено писать по этому адресу.Он может не отображаться в вашем виртуальном адресном пространстве, и, если это так, там может быть что-то важное, и перезапись приведет к поломке программы.Так что эта программа делает что-то плохое и не поддерживается, и мы не должны ожидать, что это будет работать.(В специальных средах возможно, что макет адресного пространства памяти указан и может использоваться такими способами. Такие ситуации всегда должны быть четко задокументированы, и код ограничен конкретными системами, для которых он был разработан;не подходит для использования в качестве универсального кода C.)

Во-вторых, код не делает ничего особенного, что показывает разницу между объектами с volatile и без него.Помимо ошибки, которую он записывает в 0x1234, используя int, и читает из нее, используя unsigned int, нормальное выполнение этого кода приведет к неудивительному результату, 100, если программа не падает из-за использования 0x1234.Лучшим примером может служить такая программа:

#include <stdio.h>

int main(void)
{
    int a = 1234;
    volatile int b = 5678;
    printf("Press enter to proceed.\n");
    getchar();
    printf("a = %d.\n", a);
    printf("b = %d.\n", b);
}

Затем ученикам будет предложено скомпилировать эту программу с включенной оптимизацией и отладкой, запустить ее в отладчике, прервать ее (в отладчике), пока программаожидает ввода, используйте отладчик, чтобы изменить значения a и b, а затем продолжите запуск программы.В результате программа отображает a с исходным значением 1234, но отображает b с измененным значением.(На самом деле, из-за оптимизации a может не существовать таким образом, который может быть изменен отладчиком.)

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

Значение volatileявляется то, что объект может измениться способами, обычно не известными компилятору.Поэтому, чтобы продемонстрировать, как работает volatile, необходимо изменить программу извне.Хотя отладчик является одним из способов достижения этого, предполагаемое использование volatile состоит в том, чтобы получить доступ к расположениям в адресном пространстве, которые подключены к устройствам ввода-вывода вместо обычной памяти.Эти местоположения могут измениться, когда происходит какая-либо операция ввода / вывода.Ключевое слово volatile говорит компилятору не обрабатывать объекты как обычную память, ожидая, что они могут неожиданно измениться от внешних действий.

0 голосов
/ 11 сентября 2018

Когда вы дважды читаете один и тот же регистр, компилятор может решить оптимизировать поведение.

Это может превратить код в нечто вроде этого:

unsigned  int a = * (unsigned int *) 0x1234;
unsigned  int b = a;

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

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

...