Должны ли ВСЕ глобальные переменные быть нестабильными?

В этом примере требуется ли правильность 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 я считаю, что он действителен в соответствии с абстрактной машиной C. Я ошибаюсь или это действительно?

Если это так, мне кажется, что volatile регулярно игнорируется. Это будет ничего нового!


Расширенный пример

void baz (int* i) {
    some_function (++*i);
    foo ();
    some_function (++*i);
}

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

Как говорит Роланд, я не уверен, какую часть стандарта ссылается, чтобы сказать: "Если программа что-то модифицирует, значит, объект изменен в абстрактной машине. Если программа использует значение, это означает, что это использует любое значение, которое объект имеет в абстрактной машине".

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

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

.cfi_def_cfa_register 5
subl    $24, %esp
.loc 1 67 0
movl    global_value, %eax
addl    $1, %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    $1, %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 перезагружается, как и ожидалось, между вызовами функций

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

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