Получаем ошибку 'memcpy' : cannot convert parameter 1 from 'volatile char [16]' to 'void *'
Почему волатильность это тип ? Я думал это хинт оптимизатору.
Здравствуйте, Kubyshev Andrey, Вы писали:
KA>Почему волатильность это тип ? Я думал это хинт оптимизатору.
Совсем нет. Это не подсказка оптимизатору — это обязательное требование. Причём одно из самых важных с точки зрения реализации.
В стандарте (я приведу С++2003, так как там формулировки короче) в самом начале:
1.9 Program execution
[intro.execution]
1 The semantic descriptions in this International Standard define a parameterized nondeterministic abstract
machine. This International Standard places no requirement on the structure of conforming implementa-
tions. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conform-
ing implementations are required to emulate (only) the observable behavior of the abstract machine as
explained below
6 The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and
calls to library I/O functions.
То есть компилятор обязан соблюдать всего две вещи: сохранять последовательность I/O и работу с volatile. А весь остальной стандарт — это просто уточнение к тому, как сохранять эти две вещи :) Его допустимо даже не реализовывать полностью, если компилятор сумеет добиться такого же поведения с I/O и volatile каким-либо другим магическим способом :)
Здравствуйте, denisko, Вы писали:
D>Это прежде всего, как и конст, указание тебе на то, чтобы ты подтвердил, что ты понимаешь что делаешь.
Да, но на практике выходит, что тебе постоянно приходится это делать. Я не хочу сказать, что это плохо само по себе, просто с волейтайл это перебор. Чтобы заставить код компилироваться с волейтал, мне приходиться поддерживать по паре функций/классов/<нужное вставить>, а сконст это ещё на два умножает. Из-за этого код превращается в оверблоат лажу. Хотели как лучше, а получили — как всегда.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Kubyshev Andrey, Вы писали:
KA>Ну а почему вдруг при volatile memcpy не пожет сам преобразовать массив к типу void * ?
memcpy с не-volatile указателями имеет право "халтурить", в частности, не делать вообще ничего, если результат не используется.
если бы memcpy работала бы c volatile, должна была бы читать и писать всегда по-настоящему.
есть случай, правда в WinAPI и с memset, а не с memcpy, когда оба варианта есть, это ZeroMemory и SecureZeroMemory.
ZeroMemory — обёртка над memset, если будем так обнулять более не используемые переменные, это может быть выоптимизировано.
SecureZeroMemory — явная запись через volatile, рекомендуется для обнуления буфера с sensitive информацией.
Вот, было бы странно, что преобразование к халтуре было бы неявным. В случае явного
Здравствуйте, Kubyshev Andrey, Вы писали:
KA>Почему волатильность это тип ? Я думал это хинт оптимизатору.
Тут ещё разница между "volatile void*" и "void* volatile" http://melpon.org/wandbox/permlink/kSV8ioWK8oMMLPkB
В первом случае указатель на другой тип (как и const void*), во втором — спецификатор указателя (как и void* const — "хинт")
Здравствуйте, Kubyshev Andrey, Вы писали:
KA>Ну а почему вдруг при volatile memcpy не пожет сам преобразовать массив к типу void * ?
А зачем так делать? Это будет вредно или что-нибудь сломает.
volatile память — это даже не всегда память, и будет ошибкой работать с ней так же как и с массивом на куче . Например volatile может использоваться для MMIO — в этом случае за переменной или массивом скрывается устройство и работа идёт с его регистрами. Если в коде написано volatile uint8_t* r = ...; *r = 10; *r = 20;, то это означает передать устройству сначала байт 10, потом байт 20. Если убрать volatile, то компилятор может, например, выкинуть запись 10 в память, ведь в памяти это значение всё равно перезапишется числом 20, а значит его сразу и стоит писать. Но устройство — не память, для него такое выкидывание означает катастрофу — потерялся байт!
А это всего одна однобайтовая переменная. А что должен делать memcpy с volatile памятью? Копировать её, но в каком порядке? Или какими блоками? Или насколько делать prefetch? Что должно сделать устройство, если оно ожидает массив байт, а memcpy будет записывать блоками в 128 раз большими, да ещё идя в обратном направлении от старших адресов к младшим (как делают некоторые быстрые реализации memcpy на x86-64 архитектуре)?
Тут единственный вариант сделать memcpy совместимой c volatile памятью — это жестко прописать в стандарте как работает эта функция на всех платформах и архитектурах. Что впрочем сделает её сразу же бесполезной — почти всегда никому не нужно универсальное, но невероятно медленное копирование, для которого которого жестко зафиксировано, например, что оно всегда идёт только по char'ам, только в сторону увеличения адресов, никогда не накапливает внутри себя более одного char'а (это ведь тоже может быть важно, если вдруг мы копируем из устройства в устройство). Причём дальше всё равно выяснится, что и для работы с устройствами такая функция также обычно не нужна, а нужен какой-нибудь Duff's device.
Посмотри что выше написано в стандарте — там ведь содержится очень простая идея — важен результат работы программы (то есть работа с I/O и с volatile, которые например устройства), и не важно как работает внутри программа — это вообще может быть чёрный ящик. И если два ящика дают одинаковые реакции, то стандарту удовлетворяют оба. А на остальной тысячи страниц описано как сделать ящик-образец, с которым можно сравниваться, но который не обязательно делать. И вся не-volatile память — она находится строго внутри, её не видно снаружи и компилятор не обязан даже её выделять когда написан malloc (поведение и саму возможность существования отладчиков, способных заглянуть внутрь, стандарт не описывает) — вот пример (выбрать компилятор clang 3.3, если автоматически не выбрался).
В некотором смысле ты был прав, когда назвал volatile "хинтом оптимизатору" — вот только эта подсказка относится не к самой переменной, а ко всей остальной программе — и компилятор, например, может выкинуть из неё кусок (и иногда так и делает) или заменить один алгоритм на другой (тоже иногда получается), если сумеет доказать что это не повлияет на volatile и I/O.
Относись к volatile не как к "ещё одному const", а как к интерфейсу между внешним миром и программой на C/C++. И нечего нагружать функцию memcpy знаниями об этом мире :)
Здравствуйте, Kubyshev Andrey, Вы писали:
KA>Почему волатильность это тип ? Я думал это хинт оптимизатору.
Волатильность — это не хинт, а квалификатор типа. Такой же как const. Не случайно в Стандарте они даже упоминаются вместе: cv-qualification http://en.cppreference.com/w/cpp/language/cv
Т.е. корректное использование квалификаторов позволяет программисту более точно обрисовать сценарий использования (и отловить часть ошибок сразу на этапе компиляции). А уж как именно компилятор решит всё это оптимизировать — полностью его дело.