Должны ли все глобальные переменные быть изменчивыми?
в этом примере требуется ли корректность 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--> для безопасности потоков во всех случаях (иногда вам нужна дополнительная забота об атомарности и барьерах памяти, но переменные межпоточной связи должны быть неустойчивыми...
[отредактировано]: ... если они повторно читаются и могут быть изменены другим потоком между чтениями. Это, однако,не случай, если используется любая блокировка синхронизации (мьютекс и т. д.), поскольку блокировка гарантирует, что переменные не могут быть изменены параллельная деятельность) (спасибо Р..)