Как разместить глобальную переменную по адресу, известному во время компиляции?

Я пишу код для конкретного чипа stm32 с периферийными устройствами, отображаемыми в памяти. Я случайно знаю, что один из портов GPIO, GPIOA, контролируется несколькими 32-битными целыми числами, расположенными по адресу 0x40018000. Как я могу выразить эту информацию во время компиляции?

Моя первоначальная попытка выглядела так:

struct Gpio {
    volatile uint32_t crl;
    volatile uint32_t crh;
    // ...
};

constinit Gpio& GPIOA = *reinterpret_cast<Gpio*>(0x40018000);

Кажется, это разумный способ сказать: «Ссылка GPIOA относится к объекту, расположенному по адресу памяти 0x40018000».

К сожалению, это не работает:

error: 'constinit' variable 'GPIOA' does not have a constant initializer
error: 'reinterpret_cast' from integer to pointer

Какие варианты мне доступны? Как я могу записать определенный числовой адрес памяти во время компиляции?

Предыдущие исследования:

Я бы рассматривал это за пределами стандартного переносимого кода. Он по своей сути специфичен и непереносим. 1) Почему проблемой является время компоновки, а не время компиляции? Время соединения — это обычное время назначения адреса для встроенного кода на «голом железе». 2) Обычно я делаю это на C, а не на C++. Как правило, это были методы, специфичные для цепочки инструментов. Я видел, как это делалось с использованием: специфических атрибутов компилятора в исходном коде; приведения указателей в исходном коде; конфигурации компоновщика; Модули ассемблерного кода.

Avi Berger 09.06.2024 07:56

@AviBerger BSP представляют собой библиотеки C и требуют слишком больших затрат во время выполнения. OP должен работать тоньше и быстрее, чем C.

Red.Wave 09.06.2024 08:18

Так должно быть constinit? Возможно, Gpio& GPIOA = *new(reinterpret_cast<Gpio*>(0x40018000)) Gpio; будет достаточно?

Ted Lyngmo 09.06.2024 08:21

Индустрии встраиваемых систем необходимо рассмотреть возможность метапрограммирования C++ для оптимизации и безопасности библиотек. Лучшие решения – не самые простые. Я бы посчитал класс моногосударства лучшим дизайном, чем глобальную ссылку. Более того, нужно подумать об инициализации и о том, как избежать нежелательной многократной инициализации регистров управления. Решение предполагает более сложные конструкции. В противном случае будут ненужные затраты памяти.

Red.Wave 09.06.2024 08:28

@Red.Wave Вы правы, с моей стороны плохая терминология. Я думал о чем-то гораздо более ограниченном и базовом. По сути, это настройка для сопоставления структурных переменных с периферийными устройствами, отображаемыми в памяти, и не более того, как пытается сделать OP. Я пользовался такими вещами. Теперь, когда я думаю об этом, мне кажется, что у меня нет подходящего имени, которое можно было бы использовать для этого.

Avi Berger 09.06.2024 08:42

Поставщик предоставляет заголовок stm32xxxxx.h для конкретной детали, который определяет регистры MCU. Он даже определил символ GPIOA, поэтому, если вы без необходимости решите создать свой собственный, вам следует использовать пространство имен или, по крайней мере, уникальный префикс.

Clifford 09.06.2024 09:30

@Clifford Конечно, мой настоящий код находится в пространстве имен. Это не имело отношения к вопросу о том, как разместить переменные по конкретным адресам.

Filipp 09.06.2024 15:25

@Filipp, заголовок поставщика делает во многом то же, что вы сделали с приведением C и связью C для совместимости языков.

Clifford 09.06.2024 20:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
8
769
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не знаю, как это сделать на C++, но знаю, как это сделать на C. Я думаю, будет что-то такое же.

Для С,

  1. вы можете использовать ключевое слово атрибута компилятора, чтобы поместить переменную по указанному адресу. например:

    int var __attribute__((section(".ARM.__at_0x20000000"))) = 0x1234;
    

    но он передает сценарий ссылки и компилятор ARM.

  2. для этого вы можете использовать скрипт компоновщика.

    укажите имя раздела в коде.

    int var __attribute__((section(".mysection"))) = 0x1234;
    

а затем в ваших скриптах ссылок разместить этот раздел по нужным вам адресам.

Оба не являются общими для C. В частности, это расширение GCC (и расширение Clang по совместимости).

user17732522 09.06.2024 09:31

В настоящее время язык не позволяет получить указатель/ссылку на такой объект как переменную во время компиляции. Единственный способ сформировать указатель — использовать reinterpret_cast, и это лишает инициализацию статуса постоянного выражения.

Однако не похоже, что вам действительно нужна инициализация во время компиляции. Если вас устраивает риск динамической инициализации, то constinit можно удалить, и компилятор больше не будет жаловаться. Это проблема только в том случае, если вы намерены использовать указатель/ссылку во время инициализации другой нелокальной переменной. В этом случае вы потенциально рискуете столкнуться с фиаско статического порядка инициализации.

Поведение указателей, преобразованных из целочисленных значений, полностью определяется реализацией (за исключением двустороннего приведения). Однако ничто в показанном вами коде на самом деле не создает объект по адресу, представленному указателем. Хотя поведение определяется реализацией, кажется разумным, что вам, вероятно, следует использовать Placement-new, чтобы правильно создать объект по указанному ранее адресу и получить указатель на этот объект.

Так, например:

Gpio& GPIOA = *std::construct_at(reinterpret_cast<Gpio*>(0x40018000));

Как отметил @TedLyngmo, использование std::construct_at приведет к инициализации объекта по значению, что, судя по тому, как вы определили класс, вероятно, приведет к тому, что все ваши члены будут инициализированы нулями. Если вы не хотите, чтобы этот доступ произошел, вам нужно использовать инициализацию по умолчанию, для чего вам нужно явное размещение-new:

Gpio& GPIOA = *::new(reinterpret_cast<void*>(0x40018000)) GPIOA;

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

Возможно, стоит отметить, что при использовании construct_at вместо необработанного размещения new будет записываться 0 в volatileuint32_t в Gpio. Это может заставить оборудование что-то делать.... :-)

Ted Lyngmo 09.06.2024 10:16

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

Filipp 09.06.2024 15:26

@Filipp Да, это тоже сработает, но я еще не видел ни одной библиотеки, поддерживающей это, поэтому необработанное размещение new, вероятно, лучший вариант на сегодняшний день.

Ted Lyngmo 09.06.2024 18:51

@TedLyngmo Да, я не думал об этом. Добавил примечание.

user17732522 09.06.2024 22:07

@Filipp Да, если вы используете новое размещение, то формально значение объекта будет неопределенным. Он не будет иметь никакого значения, полученного из битового состояния до нового размещения. Я не уверен, будет ли start_lifetime_at тогда гарантированно вести себя должным образом. Но в любом случае все это изначально определяется реализацией. На самом деле невозможно принять правильное решение без спецификации от поставщика компилятора. Но на практике этого не существует и никто не считает, что объекты нужно создавать правильно.

user17732522 09.06.2024 22:12
Ответ принят как подходящий

Оказывается, существует простой способ разместить глобальную переменную по определенному адресу.

Можно напрямую задать адрес символа с помощью скриптов компоновщика. В C++ просто объявите переменную следующим образом:

extern Gpio GPIO;

Не определяйте эту переменную ни в одном файле .cpp.

Затем в скрипте компоновщика укажите ему адрес:

GPIO = 0x40018000;

При связывании скажите компоновщику использовать ваш скрипт, используя опцию -T.

Теперь ваша программа должна компоноваться без каких-либо неопределенных ошибок ссылок. Символ будет относиться к указанной вами ячейке памяти. Единственное ограничение заключается в том, что для этого значения не будет никакой инициализации, но это желательно для регистра, отображаемого в памяти.

Другие вопросы по теме