В чем разница между прямым и косвенным доступом к изменчивым объектам в C? - PullRequest
0 голосов
/ 25 мая 2020

Я имею дело с аппаратными регистрами на контроллере STM32.

Я определил кучу структур вроде следующего:

#define PACKED __attribute__ ((packed))
#define ASSERT(cond) _Static_assert(cond, #cond)

typedef union {
    struct PACKED {
        uint16_t value;
        uint16_t reserved;
    };
    uint32_t bits;
} LOL_LPTIM_16_BIT_VALUE;

ASSERT(sizeof(LOL_LPTIM_16_BIT_VALUE) == sizeof(uint32_t)); // OK

Тогда у меня есть такая структура:

typedef struct PACKED {
    // ...
    volatile LOL_LPTIM_16_BIT_VALUE autoreload;
    // ...
} LOL_LPTIM;

Смещение поля autoreload в структуре согласуется с документацией. У меня также есть следующие объекты (в соответствии с документацией и заголовочными файлами, предоставленными ST):

#define LOL_LPTIM1_BASE (LOL_APB1PERIPH_BASE + 0x7C00UL)
#define LOL_LPTIM2_BASE (LOL_APB1PERIPH_BASE + 0x9400UL)

#define LOL_LPTIM1 ((volatile LOL_LPTIM *) LOL_LPTIM1_BASE)
#define LOL_LPTIM2 ((volatile LOL_LPTIM *) LOL_LPTIM2_BASE)

У меня есть структура static const, в которой хранятся эти указатели:

static const struct {
    volatile LOL_LPTIM *lptim;
} timer[2] = {
    { .lptim = LPTIM1 },
    { .lptim = LPTIM2 }
}

Теперь , когда я пишу

*(uint32_t *) &(timer[0].lptim->autoreload.bits) = 0xffff;

или

*(uint16_t *) &(timer[0].lptim->autoreload.value) = 0xffff;

, код работает правильно, но когда я пишу

timer[0].lptim->autoreload.bits = 0xffff;

(что должно быть точно эквивалентным) или

timer[0].lptim->autoreload.value = 0xffff;

, то он не работает должным образом - он работает иначе, чем косвенный вариант, и значение регистра autoreload, похоже, не установлено должным образом (периферийные устройства ведут себя иначе).

Что может быть возможной причиной этого несоответствия?

Godbolt показывает, что компилятор генерирует очень разный набор операций для этих двух случаев: https://godbolt.org/z/cno9yf

Они становятся более похожими, когда косвенная версия изменяется на

*(volatile uint32_t *) &(timer[0].lptim->autoreload.bits) = 0xffff;

(в выводе гораздо больше инструкций)

Ответы [ 2 ]

1 голос
/ 26 мая 2020

Проблема заключается в упаковке конструкций, когда она не нужна. Вы также чрезмерно используете volatile.

Будьте очень осторожны при упаковке структур и объединений, поскольку это предотвращает оптимизацию кода. Не используйте их «на всякий случай».

Вот вам и правильная версия.

https://godbolt.org/z/GNjmUX

0 голосов
/ 26 мая 2020

Когда вы делаете:

*(uint32_t *) &(timer[0].lptim->autoreload.bits) = 0xffff;

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

timer[0].lptim->autoreload.bits = 0xffff;

доступ будет непостоянным, поэтому его нельзя оптимизировать.

...