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

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

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

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

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

*(UART0CTL) &= ~1;

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

18
задан orithena 10 March 2010 в 13:32
поделиться

6 ответов

Я бы хотел придираться: мы говорим о C или C ++?

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

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

Идиоматический способ C ++ - использовать глобальную переменную:

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

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

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

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

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

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

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

17
ответ дан 30 November 2019 в 06:59
поделиться
#define UART0  ((volatile unsigned int*)0x4000C000)
#define UART0CTL (UART0 + 0x0C)
1
ответ дан 30 November 2019 в 06:59
поделиться
#define UART0CTL ((volatile unsigned int *) (UART0 + 0x30))

: - P

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

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

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

21
ответ дан 30 November 2019 в 06:59
поделиться

Другой вариант, который мне нравится для встраиваемых приложений, - это использование компоновщика для определения разделов для ваших аппаратных устройств и сопоставления вашей переменной с этими разделами. Это имеет то преимущество, что если вы ориентируетесь на несколько устройств, даже от одного и того же поставщика, такого как 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 () 
 {
 / * Инициализировать указатель стека * / 
init_sp (stack + sizeof ( stack)); 
 
 / * Инициализировать инициализированные данные * / 
memcpy (& init_data, & data, & edata - & data); 
 
 / * Turn на последовательных портах * / 
init_duart (& a); 
init_duart (& b); 
} 
 

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

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

1
ответ дан 30 November 2019 в 06:59
поделиться

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

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

(Прошу прощения, если синтаксис не совсем правильный, я действительно давно не писал на C ...)

1
ответ дан 30 November 2019 в 06:59
поделиться

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

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

UART0CTL &= ~1;

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

1
ответ дан 30 November 2019 в 06:59
поделиться