Должны ли все глобальные переменные быть изменчивыми?

в этом примере требуется ли корректность global_value объявили volatile?

int global_value = 0;

void foo () {
    ++ global_value;
}

void bar () {
    some_function (++global_value);
    foo ();
    some_function (++global_value);
}

насколько я понимаю, это volatile "призван" к указатели на отображаемую память и переменные, которые могут быть изменены сигналы (и подчеркнуто не для потокобезопасности), но легко представить, что bar может скомпилироваться примерно так:

push EAX
mov EAX, global_value
inc EAX
push EAX
call some_function
call foo
inc EAX
push EAX
call some_function
mov global_value, EAX
pop EAX

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

если так, то мне кажется, что volatile регулярно забывают. Это было бы ничего нового!


Продлен
void baz (int* i) {
    some_function (++*i);
    foo ();
    some_function (++*i);
}

int main () {
    baz (&global_value);
}

даже если bar гарантированно компилируется в правильную реализацию dont-cache-global_value, будет baz быть аналогичным образом правильным, или разрешено кэшировать энергонезависимое значение *i?

5 ответов


нет,volatile ключевое слово здесь не требуется. С global_value отображается вне функции bar, компилятор не должен предполагать, что он остается прежним, если вызывается другая функция.

[обновление 2011-07-28] я нашла хорошую цитату, которая доказывает все это. Это в ISO C99, 5.1.2.3p2, который я слишком ленив, чтобы скопировать здесь полностью. Он говорит:

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

точки последовательности включают в себя:

  • вызов функции после вычисления аргументов (6.5.2.2).
  • конец полного выражения: [...] выражение в выражение (6.8.3); [...]

вот вам и доказательство.


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

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


volatile контролирует количество и порядок операций чтения и записи в память, но даже без volatile, реализация, которая кэширует значения в качестве оптимизации, должна уважать поведение абстрактной машины. Это то, что говорит правило" как будто", поэтому оптимизации, которые не подчиняются, которые не" легко представить "для меня; -) ваш предлагаемый испускаемый код так же явно ошибочен для меня, как сказать:"запись может пойти в память без обновления или загрязнения кэша L1, поэтому будущие чтения все равно увидят старое значение в кэше". Не на одном ядре, это не будет, потому что кэш, который вел себя так, был бы сломан.

если вы называете strcpy, а затем изучить содержимое буфера назначения, компилятору не разрешается "оптимизировать", используя Предыдущее значение этого байта, хранящееся в регистре. strcpy не нужно volatile char *. Аналогично,global_value не нужно volatile.

Я полагаю, что путаница может заключаться в том, что в многопоточном коде "а затем", то есть происходит ли чтение "после" записи и, следовательно, "видит" новое значение, определяется примитивами синхронизации. В некоторых реализациях volatile имеет какое-то отношение к синхронизации из-за конкретные гарантии осуществления.

в однопоточном коде, а также в стандартах C и C++, "а затем"определяется точками последовательности, которых в коде много.


нет. Глобальные переменные не всегда должны объявляться изменчивыми.

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

EDIT: сделать его изменчивым не означает, что глобальная переменная будет потокобезопасный!

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


Volatile не требуется в этом примере. Если, например, some_function () что-то выводит, список asm кажется changes наблюдаемого поведения машины c++ и нарушает стандарт.

Я думаю, что это ошибка компилятора, здесь вывод ассемблера GCC:

.cfi_def_cfa_register 5
subl    , %esp
.loc 1 67 0
movl    global_value, %eax
addl    , %eax
movl    %eax, global_value
movl    global_value, %eax
movl    %eax, (%esp)
call    _Z13some_functioni
.loc 1 68 0
call    _Z3foov
.loc 1 69 0
movl    global_value, %eax
addl    , %eax
movl    %eax, global_value
movl    global_value, %eax
movl    %eax, (%esp)
call    _Z13some_functioni
.loc 1 70 0
leave
.cfi_restore 5

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

также летучих веществ are для потокобезопасности тоже, просто V-квалификатор не достаточно!--3--> для безопасности потоков во всех случаях (иногда вам нужна дополнительная забота об атомарности и барьерах памяти, но переменные межпоточной связи должны быть неустойчивыми...

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