Означает ли С++ стандартное описание оператора косвенного управления памятью запись не оптимизирована?
Это в основном продолжение этого вопроса. Пока это выглядит так, если у меня есть такая функция:
void SecureZeroMemory( void* ptr, size_t cnt )
{
volatile char *vptr = (volatile char *)ptr;
while (cnt) {
*vptr = 0;
vptr++;
cnt--;
}
}
и назовите его следующим образом:
{
char buffer[size];
SecureZeroMemory( buffer, size );
}
так как buffer
не объявляется volatile, не имеет значения, что используется указатель на volatile - сами данные не являются изменчивыми, поэтому запись в переменную не является наблюдаемым поведением (1.9/6) и компилятором разрешено оптимизировать их.
Однако недавно я столкнулся с утверждением, что это имеет значение только для указателя указателя. В частности, С++ 03 5.3.1/1 описывает косвенность (*) следующим образом:
Унарный * оператор выполняет косвенное обращение [...] Если тип выражения является "указателем на T", то тип результата будет "T."
Таким образом, утверждение состоит в том, что из-за использования косвенности на volatile char*
мы получаем volatile char
, и записи на них составляют наблюдаемое поведение, и уже не имеет значения, как объявляются фактические данные.
Является ли описание косвенности С++ 03 5.3.1/1 действительно гарантией того, что перезапись памяти с помощью указателя volatile T*
, как в примере выше, является наблюдаемым поведением и запрещена для оптимизации?
Ответы
Ответ 1
Я уверен, что все, что добавляет "новая" цитата, заключается в том, что *vptr
является выражением lvalue с типом volatile char
.
Тип lvalue не влияет на тип объекта, к которому относится это выражение lvalue, по той же причине, что указатель-на-const, который указывает на объект, не являющийся объектом const, не делает каким-либо образом объект Уст. Таким образом, исходный анализ не повлиял на эту цитату - объект по-прежнему не имеет изменчивого типа.
В обычном языке мы говорим, что тип *vptr
равен volatile char &
, но 5/5 говорит: "Если выражение первоначально имеет тип" ссылка на T ", тип доводится до T до любого дальнейший анализ". Предполагается, что причина, по которой *vptr
имеет тип volatile char
, а не volatile char &
- перед анализом любого выражения, которое вы удаляете ссылку из этого типа, даже если это lvalue.
[Edit: мой ответ имел обыкновение иметь некоторый текст о cv-квалификациях, несущественный для неъектных значений целочисленного типа. Это было правдой (преобразования lvalue-to-rvalue неклассовых типов отбрасывают cv-квалификаторы, 4.1/1), но неуместно (я ошибочно полагал, что, поскольку в тексте, который вы цитировали, упоминается не ссылочный тип, он говорил о типе после этого преобразование)]
Ответ 2
Это интересный вопрос. Я думаю, что цель
Стандарт заключался в том, что это должно сработать. При чтении стандарта
(С++ 03, §1.9/6,7):
Наблюдаемое поведение абстрактной машины - ее последовательность чтения и записи на изменчивые данные и обращения к библиотечному вводу/выводу функции.
Доступ к объекту, обозначенному изменчивым значением lvalue, изменяющий объект, вызов функции ввода-вывода библиотеки или вызов функция, которая выполняет любую из этих операций, эффекты, которые являются изменениями в состоянии исполнения Окружающая среда. Оценка выражения может привести к последствия. В определенных определенных точках последовательности выполнения называемые точками последовательности, все побочные эффекты предыдущих оценки должны быть полными и никаких побочных эффектов последующие оценки должны иметь место.
Разница в формулировках в двух параграфах, по-видимому,
Значительное: "наблюдаемое поведение" представляет собой последовательность чтений
и записывает изменчивые данные. В вашем случае buffer
нет
волатильные данные, поэтому компилятор, по-видимому, может свободно оптимизировать
доступ прочь. Я не думаю, что это намерение, но
это, кажется, то, что он говорит.
В вашем случае оптимизация будет особенно проста,
поскольку преобразование в летучее происходит в самой функции.
Компилятор может легко определить, что vptr
не указывает на
данные, которые фактически нестабильны. Если вы измените параметр
введите void volatile*
, тогда компилятор должен будет увидеть
как сайт вызова, так и функцию в то же время, чтобы
безопасно выполнять оптимизацию.
И, наконец, независимо от того, что говорит стандарт, компиляторы
имеют свои собственные интерпретации volatile
. На практике,
большинство, если не все компиляторы, предполагают, что вы используете
volatile
по какой-то причине, и будет генерировать машину
инструкции для записи. (На практике это все, что
они будут делать, что означает, что порядок,
видимый за пределами потока код, в котором поток
пробег остается undefined. Это не проблема для вашего использования,
но это для многих других целей.)