О volatile замолвлю я слово

Если ваш вопрос не влез ни в одну из вышеперечисленных тем, вам сюда.
Ответить
Друг Кота
Аватара пользователя
Сообщения: 5889
Зарегистрирован: Ср ноя 11, 2009 17:19:30
Откуда: Воронеж

Сообщение >TEHb< »

Охохо, а хотите ещё масла в огонь подолью? Volatile можно ещё на функции применять!
Усложнять просто. Упрощать сложно.
Контактная информация:
Реклама
Друг Кота
Сообщения: 12867
Зарегистрирован: Сб дек 18, 2021 19:25:32

Сообщение Martian »

И это правильно и логично.
Контактная информация:
Реклама
Собутыльник Кота
Аватара пользователя
Сообщения: 2516
Зарегистрирован: Пт июл 12, 2019 22:52:01

Сообщение Eddy_Em »

>TEHb<, только что проверил и на gcc, и на clang пример отсюда. Не работает volatile применительно к функции в языке С!
Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда.
Я на гитхабе, в ЖЖ
Контактная информация:
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

Eddy_Em, всё верно говоришь. volatile можно только к методам класса (тобишь C++) применять и влияет он на доступ к this. Compiler Exploer
Реклама
Эиком - электронные компоненты и радиодетали
Друг Кота
Сообщения: 12867
Зарегистрирован: Сб дек 18, 2021 19:25:32

Сообщение Martian »

да.... а старые компиляторы могли, по крайней мере, мне попадались такое в коде. Странно. Вроде же вполне логично: возвращаемое значение функции может быть бессмысленным с точки зрения оптимизатора, притом вызов как есть функции важен по каким либо-причинам.
Впрочем, это можно легко реализовать, но запись получится длиньше и страньше.
Контактная информация:
Реклама
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

Давно пора привыкнуть, что программа не то что вы о ней думаете, а то что о ней думает компилятор.

Кто объяснит, как сущность размером в 1 байт хранит значения явно большей разрядности?
Compiler Exploer
СпойлерИзображение
странные сущности.png
(193.83 КБ) 131 скачивание
Естественно, никакая другая память нигде не используется, если что. Скажу больше, там даже байта нет, просто компилятор не может признаться, что у него объект размером меньше байта :)

Добавлено after 49 minutes 27 seconds:
[uquote="Martian",url="/forum/viewtopic.php?p=4167248#p4167248"]Вроде же вполне логично: возвращаемое значение функции может быть бессмысленным с точки зрения оптимизатора, притом вызов как есть функции важен по каким либо-причинам.[/uquote]Это называется побочный эффект. Вот именно там где он создаётся и должна стоять volatile. Функции это даром не надо. Компилятор может этот эффект просчитать и воспроизвести без лишней шелухи.
Реклама
Друг Кота
Аватара пользователя
Сообщения: 5889
Зарегистрирован: Ср ноя 11, 2009 17:19:30
Откуда: Воронеж

Сообщение >TEHb< »

Eddy_Em, как конкретно писали? У меня GCC принимает объявление функции

Код: Выделить всё

void volatile init()
{
};
и работает с ней так, как я того и ожидаю. Фактически ничего не оптимизируя внутри этой функции.
Усложнять просто. Упрощать сложно.
Контактная информация:
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

Брехня! :)
Изображение
volatile.png
(29.12 КБ) 455 скачиваний
Как в самой функции пусто, так и её вызова вообще нет. volatile перед функцией относится к типу возвращаемого ей результата. Ко всему что происходит внутри это не имеет отношения.
Собутыльник Кота
Аватара пользователя
Сообщения: 2516
Зарегистрирован: Пт июл 12, 2019 22:52:01

Сообщение Eddy_Em »

>TEHb<, какая версия gcc? Я проверял на 10 и 11 — не работает volatile, да и не должен. Не помню я такого, чтобы в стандарте С были бы волатильные функции!
Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда.
Я на гитхабе, в ЖЖ
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 5889
Зарегистрирован: Ср ноя 11, 2009 17:19:30
Откуда: Воронеж

Сообщение >TEHb< »

8.3 , если не ошибаюсь. Наверно, стоило сразу уточнить, что речь про компиляцию под ARM, а конкретно Cortex-M4 ядро. Короче, ситуация такая: программа под СТМку и в этом самом ините как раз и идёт настройка всего и вся оборудования. Некоторые узлы требуют или ожидание готовности флага или некоторую фиксированную задержку. Без volatile порядок записи в регистры периферии сохранялся, а вот время "оптимизировалось". Ну и привет, ничего не работает. Добавил волшебное слово и вуаля! Все времена сохранятся как просил. Иначе, делал через прагму с конкретным указанием уровня оптимизации, но это как-то криво показалось.
Усложнять просто. Упрощать сложно.
Контактная информация:
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

Изображение
Друг Кота
Аватара пользователя
Сообщения: 5889
Зарегистрирован: Ср ноя 11, 2009 17:19:30
Откуда: Воронеж

Сообщение >TEHb< »

Ну да, вы правы. Секрет заблуждения оказался в том, что, видимо, дописал квалификатор к одной переменной одновременно с функцией. Ну и получил, конечно же, желаемый эффект. Компилятор при оптимизации на скорость по сравнению с выключенной, всё-таки немного корёжит порядок (собирает LDR-ы паровозиком), но побочки оставляет как его и попросили. Короче, крутил, вертел, и что с volatile на функции, что без, получал одинаковый выхлоп.

P.S.: ну не брехня, но точно ошибка
Усложнять просто. Упрощать сложно.
Контактная информация:
Друг Кота
Сообщения: 12867
Зарегистрирован: Сб дек 18, 2021 19:25:32

Сообщение Martian »

Тоже полазил, и где-то промелькнуло, что когда-то давно функции волатилили чтобы что-то там с возвратом void было корректно. Как говорится, уже забыл, а что вспомнил переврал, но примерно так :)) Похоже, решалась какая-то особенность компилятора и достойно теперь лишь археологов
Контактная информация:
SfS
Друг Кота
Сообщения: 19756
Зарегистрирован: Пт янв 12, 2007 11:21:39
Откуда: Томск

Сообщение SfS »

ох какая тема живучая)
Друг Кота
Аватара пользователя
Сообщения: 7377
Зарегистрирован: Пт авг 28, 2009 21:34:30
Откуда: 845-й км.

Сообщение uldemir »

А вот, если не влом поясните, как понимать... если переменная используется не явно. вот пример кода:

Код: Выделить всё

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)));
итд... 
В коде читается SPI FRAM в первых четырёх ячейках хранится 32 битное число записей, которое надо прочитать в переменную record_count, которая тут же используется в цикле for для считывания этих записей, которые идут дальше. проблема в том, что явно я туда не пишу - передаю только указатель на эту переменную. И внутри функции FRAM_dma_log_read там тоже нет явной записи. Как следует из названия, там программируется DMA, который и сделает (а может и не сделает) эту запись. Тогда получается, что формально, я эту переменную обязан обозвать волатильной? И структуру log_buffer тоже?
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

Да, конечно. Если данные в переменной появляются в результате работы прерывания или dma, то она должна быть volatile. В случае с указателями и массивами компилятору тяжело проследить, что вообще нигде нет обращений и чаще всего всё будет работать. Ваш код с FRAM скорее всего тоже. Но это недоработка компилятора и нет никаких гарантий, что завтра он не станет настолько умным, что всё почикает. Так что, действуйте формально - всё что может поменяться из DMA пусть будет volatile. Спать будете спокойней точно.
Друг Кота
Аватара пользователя
Сообщения: 7377
Зарегистрирован: Пт авг 28, 2009 21:34:30
Откуда: 845-й км.

Сообщение uldemir »

А вот не получается спокойно спать. Проблема в том, что есть только одна операция, которая требует чтобы структура log_buffer был волатильной - это чтение из FRAM, а потом идёт разбор этих данных с выводом. И вот там уже волатильность крайне вредна, потому как данные после того как считались поменяться никак не могут до следующей итерации. Нельзя ли как-нибудь этот момент корректно обойти. Может каким хитрым приведением типов? Например, указать что функции FRAM_dma_log_read передаётся указатель на волатильную переменную, а подсунуть обычную, приведенную к волатильному типу (как сейчас привожу к байтовому типу)
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

[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.
Друг Кота
Аватара пользователя
Сообщения: 7377
Зарегистрирован: Пт авг 28, 2009 21:34:30
Откуда: 845-й км.

Сообщение uldemir »

VladislavS писал(а):Тут надо смотреть что такое data_buffer_t.
А там не на что смотреть - просто 23 байта:

Код: Выделить всё

    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;
И пока их интерпретация довольно проста - числа выводятся в обычном представлении. Ну, чуть поразвесистей работа с полем sensors и whereami:
Спойлер

Код: Выделить всё

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
Конечно, тут я уже не спешу - считавание лог-файла это не realtime задача - это уже постфактум и тут оптимизация мне не требуется.
Просто сама мысль, как писать правильно?

Вот, допустим, у меня была функция чтения 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

Сообщение VladislavS »

Что-то вы всё усложняете. Функции копирования (не важно циклом, memcpy или DMA) не надо передавать информацию о том что место куда вы копируете volatile. Вы передаёте просто адрес (указатель) и функция по нему скопирует, у неё нет выбора. Для функции копирования volatile сущностью будет ваша FRAM. Всё, забыли про функцию чтения FRAM.

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

Теперь давай подумаем, ухудшит ли volatile быстродействие? Конечно, по сравнению с полностью выкинутым кодом да. Но по сравнению с правильно работающей программой нет! У вас развесистая структура, доступ к её полям по адресу этой структуры. Если вы все поля будете читать по одному разу - ничего не изменится. Если какие-то поля стоят в выражениях несколько раз, то будут лишние чтения. Но это легко парировать, прочитали поле в локальную временную переменную и подставляйте куда надо. Так часто делают со статусными регистрами в прерываниях, когда надо от наличия разных битов сделать разные действия. Вошли в прерывание, прочитали регистр статуса в локальную переменную и дальше проверяйте её сколько угодно раз, она будет в регистре процессора лежать. Да, вы должны держать в голове, что работаете с volatile сущностью и тогда штраф за её использование можно свести к нулю.
Ответить

Вернуться в «Разные вопросы по МК»