О volatile замолвлю я слово
Охохо, а хотите ещё масла в огонь подолью? Volatile можно ещё на функции применять!
Усложнять просто. Упрощать сложно.
- Реклама
- Сообщения: 12867
- Зарегистрирован: Сб дек 18, 2021 19:25:32
И это правильно и логично.
- Сообщения: 2516
- Зарегистрирован: Пт июл 12, 2019 22:52:01
>TEHb<, только что проверил и на gcc, и на clang пример отсюда. Не работает volatile применительно к функции в языке С!
Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда.
Я на гитхабе, в ЖЖ
Я на гитхабе, в ЖЖ
- Сообщения: 2567
- Зарегистрирован: Вт май 01, 2018 19:44:47
Eddy_Em, всё верно говоришь. volatile можно только к методам класса (тобишь C++) применять и влияет он на доступ к this. Compiler Exploer
- Сообщения: 12867
- Зарегистрирован: Сб дек 18, 2021 19:25:32
да.... а старые компиляторы могли, по крайней мере, мне попадались такое в коде. Странно. Вроде же вполне логично: возвращаемое значение функции может быть бессмысленным с точки зрения оптимизатора, притом вызов как есть функции важен по каким либо-причинам.
Впрочем, это можно легко реализовать, но запись получится длиньше и страньше.
Впрочем, это можно легко реализовать, но запись получится длиньше и страньше.
- Реклама
- Сообщения: 2567
- Зарегистрирован: Вт май 01, 2018 19:44:47
Давно пора привыкнуть, что программа не то что вы о ней думаете, а то что о ней думает компилятор.
Кто объяснит, как сущность размером в 1 байт хранит значения явно большей разрядности?
Compiler Exploer
Естественно, никакая другая память нигде не используется, если что. Скажу больше, там даже байта нет, просто компилятор не может признаться, что у него объект размером меньше байта 
Добавлено after 49 minutes 27 seconds:
[uquote="Martian",url="/forum/viewtopic.php?p=4167248#p4167248"]Вроде же вполне логично: возвращаемое значение функции может быть бессмысленным с точки зрения оптимизатора, притом вызов как есть функции важен по каким либо-причинам.[/uquote]Это называется побочный эффект. Вот именно там где он создаётся и должна стоять volatile. Функции это даром не надо. Компилятор может этот эффект просчитать и воспроизвести без лишней шелухи.
Кто объяснит, как сущность размером в 1 байт хранит значения явно большей разрядности?
Compiler Exploer
Спойлер
Добавлено after 49 minutes 27 seconds:
[uquote="Martian",url="/forum/viewtopic.php?p=4167248#p4167248"]Вроде же вполне логично: возвращаемое значение функции может быть бессмысленным с точки зрения оптимизатора, притом вызов как есть функции важен по каким либо-причинам.[/uquote]Это называется побочный эффект. Вот именно там где он создаётся и должна стоять volatile. Функции это даром не надо. Компилятор может этот эффект просчитать и воспроизвести без лишней шелухи.
Eddy_Em, как конкретно писали? У меня GCC принимает объявление функции
и работает с ней так, как я того и ожидаю. Фактически ничего не оптимизируя внутри этой функции.
Код: Выделить всё
void volatile init()
{
};Усложнять просто. Упрощать сложно.
- Сообщения: 2567
- Зарегистрирован: Вт май 01, 2018 19:44:47
Брехня! 
Как в самой функции пусто, так и её вызова вообще нет. volatile перед функцией относится к типу возвращаемого ей результата. Ко всему что происходит внутри это не имеет отношения.
- Сообщения: 2516
- Зарегистрирован: Пт июл 12, 2019 22:52:01
>TEHb<, какая версия gcc? Я проверял на 10 и 11 — не работает volatile, да и не должен. Не помню я такого, чтобы в стандарте С были бы волатильные функции!
Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда.
Я на гитхабе, в ЖЖ
Я на гитхабе, в ЖЖ
8.3 , если не ошибаюсь. Наверно, стоило сразу уточнить, что речь про компиляцию под ARM, а конкретно Cortex-M4 ядро. Короче, ситуация такая: программа под СТМку и в этом самом ините как раз и идёт настройка всего и вся оборудования. Некоторые узлы требуют или ожидание готовности флага или некоторую фиксированную задержку. Без volatile порядок записи в регистры периферии сохранялся, а вот время "оптимизировалось". Ну и привет, ничего не работает. Добавил волшебное слово и вуаля! Все времена сохранятся как просил. Иначе, делал через прагму с конкретным указанием уровня оптимизации, но это как-то криво показалось.
Усложнять просто. Упрощать сложно.
- Сообщения: 2567
- Зарегистрирован: Вт май 01, 2018 19:44:47
Ну да, вы правы. Секрет заблуждения оказался в том, что, видимо, дописал квалификатор к одной переменной одновременно с функцией. Ну и получил, конечно же, желаемый эффект. Компилятор при оптимизации на скорость по сравнению с выключенной, всё-таки немного корёжит порядок (собирает LDR-ы паровозиком), но побочки оставляет как его и попросили. Короче, крутил, вертел, и что с volatile на функции, что без, получал одинаковый выхлоп.
P.S.: ну не брехня, но точно ошибка
P.S.: ну не брехня, но точно ошибка
Усложнять просто. Упрощать сложно.
- Сообщения: 12867
- Зарегистрирован: Сб дек 18, 2021 19:25:32
Тоже полазил, и где-то промелькнуло, что когда-то давно функции волатилили чтобы что-то там с возвратом void было корректно. Как говорится, уже забыл, а что вспомнил переврал, но примерно так
Похоже, решалась какая-то особенность компилятора и достойно теперь лишь археологов
А вот, если не влом поясните, как понимать... если переменная используется не явно. вот пример кода:
В коде читается SPI FRAM в первых четырёх ячейках хранится 32 битное число записей, которое надо прочитать в переменную record_count, которая тут же используется в цикле for для считывания этих записей, которые идут дальше. проблема в том, что явно я туда не пишу - передаю только указатель на эту переменную. И внутри функции FRAM_dma_log_read там тоже нет явной записи. Как следует из названия, там программируется DMA, который и сделает (а может и не сделает) эту запись. Тогда получается, что формально, я эту переменную обязан обозвать волатильной? И структуру log_buffer тоже?
Код: Выделить всё
uint32_t fram_log_decode (instance_t *instance, int *none) {
static data_buffer_t log_buffer;
unsigned int record_count = 0, ii, jj, mask;
FRAM_dma_read_Start(0x00000));
FRAM_dma_log_read((uint8_t*)&record_count, sizeof(record_count)));
FRAM_dma_wait_EOT();
for (ii = 0; ii < record_count; ii++) {
FRAM_dma_log_read((uint8_t*)&log_buffer, sizeof(log_buffer)));
итд... - Сообщения: 2567
- Зарегистрирован: Вт май 01, 2018 19:44:47
Да, конечно. Если данные в переменной появляются в результате работы прерывания или dma, то она должна быть volatile. В случае с указателями и массивами компилятору тяжело проследить, что вообще нигде нет обращений и чаще всего всё будет работать. Ваш код с FRAM скорее всего тоже. Но это недоработка компилятора и нет никаких гарантий, что завтра он не станет настолько умным, что всё почикает. Так что, действуйте формально - всё что может поменяться из DMA пусть будет volatile. Спать будете спокойней точно.
А вот не получается спокойно спать. Проблема в том, что есть только одна операция, которая требует чтобы структура log_buffer был волатильной - это чтение из FRAM, а потом идёт разбор этих данных с выводом. И вот там уже волатильность крайне вредна, потому как данные после того как считались поменяться никак не могут до следующей итерации. Нельзя ли как-нибудь этот момент корректно обойти. Может каким хитрым приведением типов? Например, указать что функции FRAM_dma_log_read передаётся указатель на волатильную переменную, а подсунуть обычную, приведенную к волатильному типу (как сейчас привожу к байтовому типу)
- Сообщения: 2567
- Зарегистрирован: Вт май 01, 2018 19:44:47
[uquote="uldemir",url="/forum/viewtopic.php?p=4170777#p4170777"]Проблема в том, что есть только одна операция, которая требует чтобы структура log_buffer был волатильной - это чтение из FRAM,[/uquote]Вот тут это как раз до одного места. DMA передаётся тупо адрес, что там по нему лежит ему неинтересно.
[uquote="uldemir",url="/forum/viewtopic.php?p=4170777#p4170777"]а потом идёт разбор этих данных с выводом. И вот там уже волатильность крайне вредна,[/uquote]Тут надо смотреть что такое data_buffer_t. А то ведь в зависимости от этого вредность может варьироваться от нулевой до вредной. В крайне вредную я не верю, не ходите же вы по ней туда-сюда-обратно. Покажите определение data_buffer_t.
[uquote="uldemir",url="/forum/viewtopic.php?p=4170777#p4170777"]а потом идёт разбор этих данных с выводом. И вот там уже волатильность крайне вредна,[/uquote]Тут надо смотреть что такое data_buffer_t. А то ведь в зависимости от этого вредность может варьироваться от нулевой до вредной. В крайне вредную я не верю, не ходите же вы по ней туда-сюда-обратно. Покажите определение data_buffer_t.
А там не на что смотреть - просто 23 байта:VladislavS писал(а):Тут надо смотреть что такое data_buffer_t.
Код: Выделить всё
typedef struct data_buffer {
uint32_t Time; // 4
union{
struct{
int16_t setspeedLeft; // 2
int16_t setspeedRight; // 2
int16_t RealSpeedLeft; // 2
int16_t RealSpeedRight; // 2
int32_t StepsLeft; // 4
int32_t StepsRight; // 4
uint8_t vbat;
uint8_t sensors;
};
struct{
int coordX; // 4
int coordY; // 4
unsigned int segmentLength; // 4
int nodeNum; // 4
};
};
uint8_t whereami;
} __attribute__((packed)) data_buffer_t;
Спойлер
Код: Выделить всё
uint32_t fram_log_decode (instance_t *instance, int *none) {
data_buffer_t log_buffer;
volatile unsigned int record_count = 0;
unsigned int ii, jj, mask;
if (FRAM_dma_read_Start(0x00000)) return 1;
if (FRAM_dma_log_read((uint8_t*)&record_count, sizeof(record_count))) return 1;
FRAM_dma_wait_EOT();
instance->UART_OutString(".\r\n");
for (ii = 0; ii < record_count; ii++) {
if (FRAM_dma_log_read((uint8_t*)&log_buffer, sizeof(log_buffer))) return 1;
FRAM_dma_wait_EOT();
if (log_buffer.whereami) {
instance->UART_OutUDec(log_buffer.Time);
instance->UART_OutChar(',');
mask = 1;
for (jj = 0; jj < 8; jj++) {
if (log_buffer.sensors & mask) instance->UART_OutChar('#');
else instance->UART_OutChar(' ');
mask <<= 1;
}
instance->UART_OutChar(',');
if (log_buffer.whereami & LEFT_MASK) instance->UART_OutChar('L'); else instance->UART_OutChar(' ');
if (log_buffer.whereami & STRAIGHT_MASK) instance->UART_OutChar('S'); else instance->UART_OutChar(' ');
if (log_buffer.whereami & RIGHT_MASK) instance->UART_OutChar('R'); else instance->UART_OutChar(' ');
instance->UART_OutChar(',');
switch (log_buffer.whereami & 0x07) {
case Entrance:
instance->UART_OutString("Entrance");
break;
case Solve:
instance->UART_OutString("Solve");
break;
case Segment:
instance->UART_OutString("Segment");
break;
case Turn:
instance->UART_OutString("Turn");
break;
}
instance->UART_OutChar(',');
instance->UART_OutUDec(log_buffer.vbat);
instance->UART_OutChar(',');
if (log_buffer.setspeedLeft < 0) {
log_buffer.setspeedLeft = -log_buffer.setspeedLeft;
instance->UART_OutChar('-');
}
instance->UART_OutUDec(log_buffer.setspeedLeft);
instance->UART_OutChar(',');
if (log_buffer.setspeedRight < 0) {
log_buffer.setspeedRight = -log_buffer.setspeedRight;
instance->UART_OutChar('-');
}
instance->UART_OutUDec(log_buffer.setspeedRight);
instance->UART_OutChar(',');
if (log_buffer.RealSpeedLeft < 0) {
log_buffer.RealSpeedLeft = -log_buffer.RealSpeedLeft;
instance->UART_OutChar('-');
}
instance->UART_OutUDec(log_buffer.RealSpeedLeft);
instance->UART_OutChar(',');
if (log_buffer.RealSpeedRight < 0) {
log_buffer.RealSpeedRight = -log_buffer.RealSpeedRight;
instance->UART_OutChar('-');
}
instance->UART_OutUDec(log_buffer.RealSpeedRight);
instance->UART_OutChar(',');
if (log_buffer.StepsLeft < 0) {
log_buffer.StepsLeft = -log_buffer.StepsLeft;
instance->UART_OutChar('-');
}
instance->UART_OutUDec(log_buffer.StepsLeft);
instance->UART_OutChar(',');
if (log_buffer.StepsRight < 0) {
log_buffer.StepsRight = -log_buffer.StepsRight;
instance->UART_OutChar('-');
}
instance->UART_OutUDec(log_buffer.StepsRight);
instance->UART_OutString("\r\n");
} else {
instance->UART_OutString("Segment Length = ");
instance->UART_OutUDec(log_buffer.segmentLength);
instance->UART_OutString("\r\nCurrent index = ");
instance->UART_OutUDec(log_buffer.nodeNum);
instance->UART_OutString(" , Coord X = ");
if (log_buffer.coordX < 0) { instance->UART_OutChar('-'); instance->UART_OutUDec(-log_buffer.coordX); }
else { instance->UART_OutUDec(log_buffer.coordX); }
instance->UART_OutString(" , Coord Y = ");
if (log_buffer.coordY < 0) { instance->UART_OutChar('-'); instance->UART_OutUDec(-log_buffer.coordY); }
else { instance->UART_OutUDec(log_buffer.coordY); }
instance->UART_OutString("\r\n");
}
}
return FRAM_dma_log_Stop();
}
Код: Выделить всё
33609, # , ,Segment,71,9542,10458,10712,11172,6201,6199
33614, # , ,Segment,72,9542,10458,10712,11172,6203,6201
33619, # , ,Segment,68,9542,10458,10255,10554,6204,6202
33624, # , ,Segment,68,9542,10458,10255,10554,6206,6204
33629, # , ,Segment,71,9542,10458,10902,10614,6208,6205
33634, ## #, ,Segment,72,9542,10458,10902,10614,6209,6207
33639, ####, ,Segment,69,-10102,17000,10333,10012,6211,6208
33644, ####, ,Segment,68,6900,13100,10333,10012,6212,6210
33649, ####,L ,Solve,71,10000,10000,10040,10605,6214,6212
33654, ####,L ,Solve,72,10000,10000,10040,10605,6215,6213
33659, ####,L ,Solve,70,10000,10000,9016,10230,6216,6215
33664, ####,L ,Solve,69,10000,10000,9016,10230,6218,6216
33669, ####,L ,Solve,72,10000,10000,8425,10972,6219,6218
33674, ####,L ,Solve,72,10000,10000,8425,10972,6220,6220
33679, ####,L ,Solve,69,10000,10000,7808,10515,6221,6221
33684, ####,L ,Solve,69,10000,10000,7808,10515,6222,6223
33689, ####,L ,Solve,72,10000,10000,7391,11053,6223,6224
33694, ####,L ,Solve,72,10000,10000,7391,11053,6224,6226
33699, ####,L ,Solve,69,10000,10000,6825,10533,6225,6228
33704, # ##,L ,Solve,68,10000,10000,6825,10533,6226,6229
33709, # ,L ,Solve,72,10000,10000,7194,11106,6227,6231
33714, # ,L ,Solve,72,10000,10000,7194,11106,6228,6233
33719, # ,L ,Solve,69,10000,10000,6855,10557,6229,6234
33724, # ,L ,Solve,68,10000,10000,6855,10557,6231,6236
33729, # ,L ,Solve,71,10000,10000,7543,10789,6232,6237
33734, # ,L ,Solve,72,10000,10000,7543,10789,6233,6239
33739, # ,L ,Solve,68,10000,10000,7529,10208,6234,6240
33744, # ,L ,Solve,67,10000,10000,7529,10208,6235,6242
33749, ## ,L ,Solve,71,10000,10000,8199,10715,6237,6244
33754, ## ,L ,Solve,72,10000,10000,8199,10715,6238,6245
33759, ## ,L ,Solve,68,10000,10000,8409,10054,6239,6247
Segment Length = 143
Current index = 4 , Coord X = 664 , Coord Y = 163
33765, ## , S ,Solve,67,10000,10000,8409,10054,6241,6248
33769, ## , S ,Solve,71,10000,10000,9429,10464,6242,6250
33774, ## , S ,Solve,72,10000,10000,9429,10464,6243,6251
33779, ## , S ,Solve,68,10000,10000,9245,9851,6245,6253
33784, ## , S ,Solve,67,10000,10000,9245,9851,6246,6254
33789, ## , S ,Solve,71,10000,10000,10032,10219,6248,6256
Просто сама мысль, как писать правильно?
Вот, допустим, у меня была функция чтения FRAM сделанная по-тупому:
Код: Выделить всё
unsigned int FRAM_log_read(uint8_t *rd_data_ptr, unsigned int wr_data_size) {
while (wr_data_size--) {
*rd_data_ptr++ = get_byte_from_fram();
}
}Спойлер
Код: Выделить всё
unsigned int FRAM_dma_log_read(uint8_t *rd_data_ptr, unsigned int rd_data_size) {
if (UCB0NSS != 0) return 1;
while (fram_write_busy) continue;
EUSCI_B0->RXBUF;
MAP_DMA_assignChannel(DMA_CH0_EUSCIB0TX0);
MAP_DMA_assignChannel(DMA_CH1_EUSCIB0RX0);
MAP_DMA_setChannelControl(DMA_CH0_EUSCIB0TX0 | UDMA_PRI_SELECT,
UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_1);
MAP_DMA_setChannelControl(DMA_CH1_EUSCIB0RX0 | UDMA_PRI_SELECT,
UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_1);
/* Setup the RX transfer characteristics & buffers */
MAP_DMA_setChannelTransfer(DMA_CH1_EUSCIB0RX0 | UDMA_PRI_SELECT,
UDMA_MODE_BASIC,
(void *) MAP_SPI_getReceiveBufferAddressForDMA(EUSCI_B0_BASE),
rd_data_ptr,
rd_data_size);
/* Setup the TX transfer characteristics & buffers */
MAP_DMA_setChannelTransfer(DMA_CH0_EUSCIB0TX0 | UDMA_PRI_SELECT,
UDMA_MODE_BASIC, (void *) rd_data_ptr,
(void *) MAP_SPI_getTransmitBufferAddressForDMA(EUSCI_B0_BASE),
rd_data_size);
/* Assigning/Enabling Interrupts */
MAP_DMA_assignInterrupt(DMA_INT0, DMA_CHANNEL_1);
MAP_Interrupt_enableInterrupt(INT_DMA_INT0);
MAP_DMA_enableInterrupt(INT_DMA_INT0);
fram_write_busy = 1;
MAP_DMA_enableChannel(1);
MAP_DMA_enableChannel(0);
return 0;
}
- Сообщения: 2567
- Зарегистрирован: Вт май 01, 2018 19:44:47
Что-то вы всё усложняете. Функции копирования (не важно циклом, memcpy или DMA) не надо передавать информацию о том что место куда вы копируете volatile. Вы передаёте просто адрес (указатель) и функция по нему скопирует, у неё нет выбора. Для функции копирования volatile сущностью будет ваша FRAM. Всё, забыли про функцию чтения FRAM.
Переходим к log_buffer. Он должен быть volatile. Потому что при копироапнии в него данных через DMA логика компилятора будет такая: о, смотри ка, глобальный буфер, инициализирован нулями, понятно же что при любом чтении из него будет ноль, ну и буду везде его подставлять без чтения, а там глядишь и половину кода можно даже не компилировать, а выкинуть - красота! Нет, говорим мы, там будет лежать для тебя сюрприз, будь добр взять его и честно отработать.
Теперь давай подумаем, ухудшит ли volatile быстродействие? Конечно, по сравнению с полностью выкинутым кодом да. Но по сравнению с правильно работающей программой нет! У вас развесистая структура, доступ к её полям по адресу этой структуры. Если вы все поля будете читать по одному разу - ничего не изменится. Если какие-то поля стоят в выражениях несколько раз, то будут лишние чтения. Но это легко парировать, прочитали поле в локальную временную переменную и подставляйте куда надо. Так часто делают со статусными регистрами в прерываниях, когда надо от наличия разных битов сделать разные действия. Вошли в прерывание, прочитали регистр статуса в локальную переменную и дальше проверяйте её сколько угодно раз, она будет в регистре процессора лежать. Да, вы должны держать в голове, что работаете с volatile сущностью и тогда штраф за её использование можно свести к нулю.
Переходим к log_buffer. Он должен быть volatile. Потому что при копироапнии в него данных через DMA логика компилятора будет такая: о, смотри ка, глобальный буфер, инициализирован нулями, понятно же что при любом чтении из него будет ноль, ну и буду везде его подставлять без чтения, а там глядишь и половину кода можно даже не компилировать, а выкинуть - красота! Нет, говорим мы, там будет лежать для тебя сюрприз, будь добр взять его и честно отработать.
Теперь давай подумаем, ухудшит ли volatile быстродействие? Конечно, по сравнению с полностью выкинутым кодом да. Но по сравнению с правильно работающей программой нет! У вас развесистая структура, доступ к её полям по адресу этой структуры. Если вы все поля будете читать по одному разу - ничего не изменится. Если какие-то поля стоят в выражениях несколько раз, то будут лишние чтения. Но это легко парировать, прочитали поле в локальную временную переменную и подставляйте куда надо. Так часто делают со статусными регистрами в прерываниях, когда надо от наличия разных битов сделать разные действия. Вошли в прерывание, прочитали регистр статуса в локальную переменную и дальше проверяйте её сколько угодно раз, она будет в регистре процессора лежать. Да, вы должны держать в голове, что работаете с volatile сущностью и тогда штраф за её использование можно свести к нулю.



