Осуществляет ли доступ к объявленному энергонезависимому объекту посредством изменчивого ссылочного/указательного устройства волатильные правила для упомянутых обращений?

Это будет длинный, чтобы контекстуализировать его и предоставить как можно больше информации, я должен пробираться через различные ссылки и цитаты - как это часто бывает, когда мы входим в C/С++ Standard Rabbit Hole. Если у вас есть лучшие цитаты или какие-либо другие улучшения в этом посте, пожалуйста, дайте мне знать. Но для подведения итогов, вы можете обвинить @ zwol, чтобы я опубликовал это;-), и цель состоит в том, чтобы найти правду из двух предложения:

  • Сделайте C и (путем импорта, см. комментарии) Стандарты С++ требуют, чтобы обращения через volatile * или volatile & должны ссылаться на объект, первоначально объявленный volatile, чтобы иметь volatile семантику?
  • Или обращается к объекту, не содержащему volatile, через указатель/ссылку volatile, достаточный/предполагаемый, чтобы заставить указанные обращения вести себя так, как если бы объект был объявлен volatile?

И в любом случае, если (по-видимому) формулировка несколько неоднозначна по сравнению с намерением - , можем ли мы понять, что в самих Стандартах было четко указано?

1-я из этих взаимно исключающих интерпретаций более распространена, и это не совсем без оснований. Тем не менее, я надеюсь показать, что существует значительное количество "разумных сомнений" в пользу второго - особенно когда мы возвращаемся к некоторым предыдущим отрывкам в "Обосновании" и "Рабочих документах".


Принятая мудрость: сам упомянутый объект должен быть объявлен volatile

Вчера популярный вопрос Является ли определение "volatile" этой изменчивой или имеет GCC, имеющую некоторые стандартные проблемы соответствия? возникла, предположив, что ссылка volatile даст volatile на реферере не volatile, но обнаружив, что он этого не сделал или сделал в разной степени и непредсказуемым образом.

Принятый ответ изначально заключался в том, что имел значение только заявленный тип референта. Это и большинство комментариев, похоже, согласны с тем, что эквивалентные принципы находятся в игре, как мы хорошо знаем для const: поведение будет только volatile (или определено вообще), если ссылка имеет ту же самую cv-квалификацию, что и упомянутый объект:

Ключевым словом в этом проходе является объект. volatile sig_atomic_t flag; - неустойчивый объект. *(volatile char *)foo - это просто доступ через l volue с изменчивой квалификацией, и стандарт не требует наличия специальных эффектов. - zwol

Эта интерпретация, как представляется, довольно широко распространена, как видно в ответах на этот похожий, но, надеюсь, не дублирующий вопрос: Требования к поведению указателя к летучей ориентации к энергонезависимому объекту. Но там есть неопределенность: сразу же после ответа "нет" , он говорит "возможно" ! В любом случае... пусть проверяет Стандарт, чтобы увидеть, на что основан "нет" .


Что говорит стандарт... или не

C11, N1548, §6.7.3. В то время как ясно, что UB получает доступ к объекту , определенному с помощью volatile или const, с помощью указателя, который не делится указанным квалификатором...

6 Если предпринимается попытка изменить объект, определенный с помощью const -qualified type, используя значение lvalue с не-t211 > -qualified type, поведение undefined, Если делается попытка ссылаться на объект, определенный с volatile -qualified type, используя значение lvalue с классом volatile-, поведение undefined. (133)

... Стандарт, похоже, явно не упоминает противоположный сценарий, а именно для volatile. Более того, при суммировании volatile и операций на нем он теперь говорит об объекте, в котором имеет volatile -qualified type:

7 Объект , имеющий volatile -qualified type, может быть изменен способами, неизвестными реализации или имеющими другие неизвестные побочные эффекты. Поэтому любое выражение, относящееся к такому объекту, должно оцениваться строго в соответствии с правилами абстрактной машины, как описано в 5.1.2.3. Кроме того, в каждой точке последовательности значение, сохраненное последним в объекте, должно совпадать с значением, предписанным абстрактной машиной, за исключением измененных неизвестными факторами, упомянутыми ранее. (134) Что представляет собой доступ к объекту с volatile -qualified тип определяется реализацией.

Предполагаем ли мы, что "имеет" эквивалентно "было определено с"? или может "есть" см. комбинацию объектных и ссылочных квалификаторов?

В комментариях комментировался вопрос с такой формулировкой:

От n1548 §6.7.3 ¶6 стандарт использует фразу "объект, определенный с помощью нестабильного типа", чтобы отличить его от "lvalue с нестабильным". К сожалению, этот "объект, определенный с" по сравнению с "lvalue" разграничением не переносится, а в стандарте затем используется "объект, который имеет нестабильный тип", и говорит, что "что представляет собой доступ к объекту, который имеет нестабильный тип определяется реализацией" (для ясности можно было сказать "lvalue" или "объект, определенный с" ). Ну что ж. - Дитрих Эпп

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


Разумное сомнение: Is/был указателем/ссылкой volatile, предназначенным для предоставления семантики volatile при ее разыменовании?

В вышеупомянутом ответе есть комментарий, в котором автор приводит более раннее выражение Комитета, в котором ставится под сомнение идея "ссылка должна соответствовать референту":

Интересно, что там есть одно предложение [ C99 Обоснование для volatile], что означает, что комитет означает *(volatile T*)x, чтобы заставить один доступ к x рассматриваться как изменчивый; но фактическая формулировка стандарта не достигает этого. - zwol

Мы можем найти немного больше информации об этом фрагменте Обоснования из второго вышеупомянутого потока: Требования к поведению указателя к летучести, указывающего на энергонезависимый объект

С другой стороны, этот пост цитирует из 6.7.3 Обоснования для международного стандарта - Языки программирования - C:

Приведение значения к квалифицированному типу не влияет; квалификация   (volatile, say) может не влиять на доступ, так как это произошло   до случая. Если необходим доступ к энергонезависимому объекту   используя изменчивую семантику, метод заключается в том, чтобы указать адрес   объект к соответствующему типу-указателю, а затем разыменованию   этот указатель.

- philipxy

И из этот поток байтов, мы ссылаемся на C99 s6.7.3 p3 - aka C11 p4 - и этот анализ:

Данный параграф находится непосредственно перед разделом 6.7.3.1 в обоснованном документе. Если вам также нужно указать из самого стандартного документа, укажите 6.7.3 p3:

Свойства, связанные с квалифицированными типами, имеют смысл только для выражений, которые являются lvalues.

Выражение (volatile WHATEVER) non_volatile_object_identifierне является значением lvalue, поэтому "изменчивый" классификатор не имеет смысла.

Обратно, выражение * (volatile WHATEVER *) & non_volatile_object_identifierявляется lvalue (он может быть помещен в левую часть оператора присваивания), поэтому свойство "изменчивого" квалификатора имеет в этом случае свое предполагаемое значение.

- Тим Ренч

Существует очень конкретная демонстрация, поддерживающая эту идею, с учетом 1-го связанного вопроса, в WG Paper N1381. Это ввело приложение memset_s(), чтобы сделать то, что хотел OP - гарантировать неавтоматизированное заполнение памяти. При обсуждении возможных реализаций, похоже, эта идея поддерживает идею опускания любого требования - использование указателем volatile для изменения объекта не volatile должно генерировать код на основе классификатора указатель, независимо от указанного объекта...

  1. Независимое от платформы решение "безопасная память":
void *secure_memset(void *v, int c , size_t n) {
    volatile unsigned char *p = v;
    while (n--) *p++ = c;
    return v;
}

Этот подход предотвратит оптимизацию очистки памяти и он должен работать на любой стандартной совместимой платформе.

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

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


Кто прав?

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

  • Обоснование и N1381 ошибочно или беспорядочно сформулированы или
  • они были специально аннулированы ретроактивно... или
  • Стандарт ошибочно или беспорядочно сформулирован.

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

Ответ 1

Осуществляет ли доступ к объявленному энергонезависимому объекту посредством изменчивого ссылочного/указательного устройства волатильные правила при упомянутых обращений?

volatile не означает то же самое в C и С++. Стандарт С++ позволяет получать доступ через летучие lvalues. [1] В нем говорится, что он предполагает, что это будет таким же, как поведение С. И это поведение, описанное в Обосновании C. Тем не менее, в C-стандарте говорится, что доступ к объектам, объявленным волатильно, является наблюдаемым. (Обратите внимание, что доступ к объявленному во летущем объявлении объекту через нелетучее значение l равен undefined.)

Однако. Существует отчет о дефекте, который по существу имеет соглашение о комитете (хотя и все еще открыто), которое должен сказать стандарт, и что намерение всегда было и что реализации всегда отражали, что важна не волатильность объекта (на Стандарт), а волатильность (lvalue) доступа (за Обоснование).

Сводка отчета о дефектах для версии C11 1.10 Дата: апрель 2016 DR 476 изменчивая семантика для lvalues ​​04/2016 Открыть

Конечно, то, что делается для наблюдаемого поведения, зависит от реализации.

На самом деле нет никакой двусмысленности. Просто люди не могут поверить в то, что поведение C Standard может быть тем, чем оно является, потому что это не историческое использование pre- volatile (когда значения адреса-литерала были выбраны из летучих объектов), как предполагалось Обоснование, реализованное компиляторами до и после, как интерпретировано и описано стандартом С++, исправлено в DR. Аналогично, стандарт ясен тем, что он не говорит, что энергонезависимые обращения наблюдаемы, поэтому они не являются. (И "побочный эффект" - это термин, используемый при определении частичного порядка оценки.)

[1] Или, по крайней мере, надеюсь, теперь это происходит. Из комментария underscore_d:

Для С++ см. также P0612R0: комментарий NB CH 2: volatile, который был принят в этом месяце, чтобы очистить некоторые оставшиеся разговоры о "изменчивых объектах" в стандарте С++, когда действительно доступ через летучие glvalues ​​означает то, что он имел в виду (как, предположительно/надеюсь, что означает C).

Ответ 2

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

Я предполагаю, что основной вопрос: "Как абстрактно мы ожидаем, что модель памяти будет?". Квалифицировав не-vol-указатель как изменчивый, мы, похоже, просим компилятор "напрямую писать в I/O или память". Это нормально, но если компилятор ранее вывел, что "памяти" не существует, что делать? Откат и создание памяти или игнорирование вас?

Мне кажется, что эти следующие два случая сильно различаются в намерениях:

Ввод/вывод с памятью

volatile unsigned char * const uart_base = (volatile unsigned char *)0x10000;

Это явно предназначено для информирования компилятора о том, что имеется карта памяти uart по адресу 0x10000.

Удаление хэшей паролей

void *secure_memset(void *v, int c , size_t n) {
    volatile unsigned char *p = v;
    while (n--) *p++ = c;
    return v;
} 

Это явно предназначено для обеспечения того, чтобы память в v до (int *) v + n была фактически изменена до возвращения функции.

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

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

Спасибо. Поскольку адрес берется, не объект, требуемый для хранения памяти?

gcc, похоже, согласен с вами:

#include <cstdint>
#include <cstring>

void * clearmem(void* p, std::size_t len)
{
  auto vp = reinterpret_cast<volatile char*>(p);
  while (len--) {
    *vp++ = 0;
  }
  return p;
}

struct A
{
  char sensitive[100];

  A(const char* p)
  {
    std::strcpy(sensitive, p);
  }

  ~A() {
    clearmem(&sensitive[0], 100);
  }
};

void use_privacy(A a)
{
  auto b = a;
}


int main()
{
  A a("very private");
  use_privacy(a);
}

дает:

clearmem(void*, unsigned long):
        leaq    (%rdi,%rsi), %rax
        testq   %rsi, %rsi
        je      .L4
.L5:
        movb    $0, (%rdi)
        addq    $1, %rdi
        cmpq    %rax, %rdi
        jne     .L5
.L4:
        xorl    %eax, %eax
        ret
use_privacy(A):
        leaq    -120(%rsp), %rax
        leaq    100(%rax), %rdx
.L10:
        movb    $0, (%rax)
        addq    $1, %rax
        cmpq    %rdx, %rax
        jne     .L10
        ret
main:
        leaq    -120(%rsp), %rax
        leaq    100(%rax), %rdx
.L13:
        movb    $0, (%rax)
        addq    $1, %rax
        cmpq    %rdx, %rax
        jne     .L13
        leaq    -120(%rsp), %rax
        leaq    100(%rax), %rdx
.L14:
        movb    $0, (%rax)
        addq    $1, %rax
        cmpq    %rdx, %rax
        jne     .L14
        leaq    -120(%rsp), %rax
        leaq    100(%rax), %rdx
.L15:
        movb    $0, (%rax)
        addq    $1, %rax
        cmpq    %rdx, %rax
        jne     .L15
        xorl    %eax, %eax
        ret

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

Ответ 3

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

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

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

То, что я нахожу странным, заключается в том, что за последние несколько лет люди настолько заинтересовались тем, что стандарт позволяет компиляторам реализовать бесполезную семантику для некоторых конструкций с целью повышения производительности кода, который не требует, чтобы конструкции были полезны, когда такой подход казался бы уступающим практически всем способом реализации полезной семантики по умолчанию, но предоставляя программистам, которым не нужна такая семантика, чтобы отказаться от них с помощью командной строки или директив #pragma. Если программа включает директиву [гипотетический] #pragma gcc_loose_volatile, gcc может делать все, что угодно, независимо от того, как иначе интерпретировать Стандартные требования к volatile, и если в нее не включена такая директива, бесполезная семантика будет бесполезной независимо от того, запретил ли их стандарт или нет.

Ответ 4

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