Если ваш вопрос не влез ни в одну из вышеперечисленных тем, вам сюда.
Ответить

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

Пн янв 24, 2022 17:16:51

Охохо, а хотите ещё масла в огонь подолью? Volatile можно ещё на функции применять!

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

Пн янв 24, 2022 17:20:42

И это правильно и логично.

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

Пн янв 24, 2022 17:50:27

>TEHb<, только что проверил и на gcc, и на clang пример отсюда. Не работает volatile применительно к функции в языке С!

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

Пн янв 24, 2022 18:14:55

Eddy_Em, всё верно говоришь. volatile можно только к методам класса (тобишь C++) применять и влияет он на доступ к this. Compiler Exploer

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

Пн янв 24, 2022 18:59:26

да.... а старые компиляторы могли, по крайней мере, мне попадались такое в коде. Странно. Вроде же вполне логично: возвращаемое значение функции может быть бессмысленным с точки зрения оптимизатора, притом вызов как есть функции важен по каким либо-причинам.
Впрочем, это можно легко реализовать, но запись получится длиньше и страньше.

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

Пн янв 24, 2022 20:36:31

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

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

Естественно, никакая другая память нигде не используется, если что. Скажу больше, там даже байта нет, просто компилятор не может признаться, что у него объект размером меньше байта :)

Добавлено after 49 minutes 27 seconds:
Вроде же вполне логично: возвращаемое значение функции может быть бессмысленным с точки зрения оптимизатора, притом вызов как есть функции важен по каким либо-причинам.
Это называется побочный эффект. Вот именно там где он создаётся и должна стоять volatile. Функции это даром не надо. Компилятор может этот эффект просчитать и воспроизвести без лишней шелухи.

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

Вт янв 25, 2022 10:02:34

Eddy_Em, как конкретно писали? У меня GCC принимает объявление функции
Код:
void volatile init()
{
};

и работает с ней так, как я того и ожидаю. Фактически ничего не оптимизируя внутри этой функции.

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

Вт янв 25, 2022 10:52:55

Брехня! :)
Изображение
volatile.png
(29.12 KiB) Скачиваний: 333
Как в самой функции пусто, так и её вызова вообще нет. volatile перед функцией относится к типу возвращаемого ей результата. Ко всему что происходит внутри это не имеет отношения.

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

Вт янв 25, 2022 11:03:23

>TEHb<, какая версия gcc? Я проверял на 10 и 11 — не работает volatile, да и не должен. Не помню я такого, чтобы в стандарте С были бы волатильные функции!

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

Вт янв 25, 2022 11:22:32

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

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

Вт янв 25, 2022 11:29:19

Изображение

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

Ср янв 26, 2022 18:22:03

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

P.S.: ну не брехня, но точно ошибка

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

Ср янв 26, 2022 19:22:14

Тоже полазил, и где-то промелькнуло, что когда-то давно функции волатилили чтобы что-то там с возвратом void было корректно. Как говорится, уже забыл, а что вспомнил переврал, но примерно так :)) Похоже, решалась какая-то особенность компилятора и достойно теперь лишь археологов

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

Чт янв 27, 2022 09:21:43

ох какая тема живучая)

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

Сб янв 29, 2022 19:22:51

А вот, если не влом поясните, как понимать... если переменная используется не явно. вот пример кода:
Код:
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 тоже?

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

Сб янв 29, 2022 19:39:46

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

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

Сб янв 29, 2022 21:35:50

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

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

Сб янв 29, 2022 22:41:06

Проблема в том, что есть только одна операция, которая требует чтобы структура log_buffer был волатильной - это чтение из FRAM,
Вот тут это как раз до одного места. DMA передаётся тупо адрес, что там по нему лежит ему неинтересно.

а потом идёт разбор этих данных с выводом. И вот там уже волатильность крайне вредна,
Тут надо смотреть что такое data_buffer_t. А то ведь в зависимости от этого вредность может варьироваться от нулевой до вредной. В крайне вредную я не верю, не ходите же вы по ней туда-сюда-обратно. Покажите определение data_buffer_t.

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

Вс янв 30, 2022 12:11:20

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;
}
Но тут уже всё, явного присваивания нет и компилятор и/или линкер берут и просто выкидывают эти куски кода. Тогда получается, что надо теперь бегать по всем исходникам искать эти вызовы и расставлять модификаторы?

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

Вс янв 30, 2022 12:50:38

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

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

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