Информация об изменениях

Сообщение Re[3]: почему волатильность меняет тип? от 03.10.2014 8:21

Изменено 03.10.2014 23:03 watchmaker

Здравствуйте, 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 знаниями об этом мире :)
Re[3]: почему волатильность меняет тип?
Здравствуйте, 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 знаниями об этом мире :)