Какой самый короткий код для записи непосредственно на адрес памяти в C/С++?

Я пишу код системного уровня для встроенной системы без защиты памяти (на ARM Cortex-M1, компиляция с gcc 4.3) и нужно читать/записывать непосредственно в регистр с отображением памяти. Пока мой код выглядит так:

#define UART0     0x4000C000
#define UART0CTL  (UART0 + 0x30)

volatile unsigned int *p;
p = UART0CTL;
*p &= ~1;

Есть ли более короткий способ (короче в коде, я имею в виду), который не использует указатель? Я ищу способ написать фактический код назначения как короткий (это было бы хорошо, если бы мне пришлось использовать больше #defines):

*(UART0CTL) &= ~1;

Все, что я пробовал до сих пор, закончил с gcc, жалуясь, что он не мог присвоить значение lvalue...

Ответы

Ответ 1

Я хотел бы быть nitpick: мы говорим на C или С++?

Если C, я охотно отвечаю на ответ Криса (и я бы хотел, чтобы тег С++ был удален).

Если С++, я рекомендую использовать эти неприятные C-Casts и #define вообще.

Идиоматическим способом С++ является использование глобальной переменной:

volatile unsigned int& UART0 = *((volatile unsigned int*)0x4000C000);
volatile unsigned int& UART0CTL = *(&UART0 + 0x0C);

Объявляю глобальную переменную типизированная, которая будет подчиняться правилам области (в отличие от макросов).

Его можно легко использовать (не нужно использовать *()) и, следовательно, еще короче!

UART0CTL &= ~1; // no need to dereference, it already a reference

Если вы хотите, чтобы он был указателем, это будет:

volatile unsigned int* const UART0 = 0x4000C000; // Note the const to prevent rebinding

Но какой смысл использовать указатель const, который не может быть нулевым? Это семантически, почему были созданы ссылки для.

Ответ 2

#define UART0CTL ((volatile unsigned int *) (UART0 + 0x30))

: - P

Отредактировано для добавления: О, в ответ на все комментарии о том, как вопрос помечен как С++, так и C, вот С++-решение.:-P

inline unsigned volatile& uart0ctl() {
    return *reinterpret_cast<unsigned volatile*>(UART0 + 0x30);
}

Это может быть застряло прямо в файле заголовка, точно так же, как макрос C-стиля, но вы должны использовать синтаксис вызова функции для его вызова.

Ответ 3

#define UART0  ((volatile unsigned int*)0x4000C000)
#define UART0CTL (UART0 + 0x0C)

Ответ 4

Вы можете пойти дальше, чем Крис, если хотите, чтобы аппаратные регистры выглядели как простые старые переменные:

#define UART0     0x4000C000
#define UART0CTL (*((volatile unsigned int *) (UART0 + 0x30)))

UART0CTL &= ~1;

Это вопрос вкуса, который может быть предпочтительным. Я работал в ситуациях, когда команда хотела, чтобы регистры выглядели как переменные, и я работал над кодом, где добавленное разыменование считалось "слишком скрытым", поэтому макрос для регистра оставался как указатель, который должен был разыменовываться явно (как в ответе Криса).

Ответ 5

Мне нравится указывать фактические контрольные биты в структуре, а затем назначать это управляющему адресу. Что-то вроде:

typedef struct uart_ctl_t {
    unsigned other_bits : 31;
    unsigned disable : 1;
};
uart_ctl_t *uart_ctl = 0x4000C030;
uart_ctl->disable = 1;

(Извините, если синтаксис не совсем прав, я на самом деле не закодирован в C довольно долго...)

Ответ 6

Другим вариантом, который мне нравится для встроенных приложений, является использование компоновщика для определения разделов для ваших жестких устройств и отображения вашей переменной в эти разделы. Это имеет то преимущество, что если вы нацеливаете на несколько устройств, даже от того же поставщика, такого как TI, вам обычно придется изменять файлы компоновщика на устройстве по принципу устройства. то есть разные устройства в одном и том же семействе имеют разное количество внутренней памяти с прямой привязкой, а плата на борту может иметь разные объемы бара и оборудование в разных местах. Вот пример из документации GCC:

Обычно компилятор помещает объекты, которые он генерирует в таких разделах, как data и bss. Иногда, однако, вам нужны дополнительные разделы, или вам нужно, чтобы определенные специальные переменные отображались в специальных разделах, например, для сопоставления с специальным оборудованием. Атрибут section указывает, что переменная (или функция) живет в определенном разделе. Например, эта небольшая программа использует несколько имен определенных разделов:

      struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
      struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
      char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
      int init_data __attribute__ ((section ("INITDATA")));

      main()
      {
        /* Initialize stack pointer */
        init_sp (stack + sizeof (stack));

        /* Initialize initialized data */
        memcpy (&init_data, &data, &edata - &data);

        /* Turn on the serial ports */
        init_duart (&a);
        init_duart (&b);
      }

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

Вы можете использовать атрибут раздела с инициализированными или неинициализированными глобальными переменными, но компоновщик требует, чтобы каждый объект определялся один раз, за ​​исключением того, что неинициализированные переменные ориентировочно переходят в общую (или bss) секцию и могут быть умножены "определены". Использование атрибута section изменит раздел, в который входит переменная, и может привести к тому, что компоновщик выдает ошибку, если неинициализированная переменная имеет несколько определений. Вы можете принудительно инициализировать переменную с помощью флага -fno-common или атрибута nocommon.