GCC C++ (ARM) и постоянный указатель на поле структуры

Допустим, есть простой тестовый код

typedef struct
{
    int first;
    int second;
    int third;
} type_t;

#define ADDRESS 0x12345678

#define REGISTER ((type_t*)ADDRESS)

const int data = (int)(&REGISTER->second)*2;

int main(void)
{
    volatile int data_copy;

    data_copy = data;

    while(1) {};
}

Который скомпилирован в CodeSourcery G++ (gcc 4.3.2) для «голого железа» ARM. Он также имеет очень стандартный скрипт компоновщика.

При компиляции в C (как main.c) объект «данные» попадает во Flash, как и ожидалось. При компиляции на C++ (как main.cpp) этот объект помещается в ОЗУ, и добавляется дополнительный код, который не делает ничего, кроме копирования значения из Flash в ОЗУ (значение уже вычислено, просто скопируйте!). Таким образом, компилятор может вычислить адрес, но почему-то не хочет «просто использовать его». Корень проблемы в умножении адреса - без умножения на "*2" обе версии работают как положено - "данные" помещаются во Flash. Также - когда "данные" объявлены как:

const int data = (int)(REGISTER)*2;

тоже все нормально.

Все файлы для компиляции C и C++ идентичны, разница только в вызове компилятора - g++ для main.cpp, gcc для main.c (с разницей в уровне предупреждений, а в c++ отключены RTTI и исключения).

Есть ли простой и элегантный способ преодолеть эту «проблему C++»? Мне нужны такие операции для создания константных массивов адресов битов в битовой области Cortex-M3. Это ошибка или, может быть, это какое-то странное ограничение компилятора C++?

Я знаю, что могу создавать объекты данных в файлах "C" и просто "extern" включать их в C++, но это не очень элегантно [;

Спасибо за помощь!


person Freddie Chopin    schedule 16.08.2009    source источник


Ответы (7)


У вас несколько проблем. Почему вы берете адрес, конвертируете его в целое число и умножаете на 2? Вы никогда не должны умножать адреса или сохранять адреса как целые числа. Единственная арифметика, которую вы когда-либо должны делать с указателями, — это вычитать их (чтобы получить смещение) или добавлять указатель со смещением, чтобы получить другой указатель.

В C++ правила инициализации глобальных значений немного менее строгие, чем в C. В C значения должны быть инициализированы как константы времени компиляции; в результате компилятор может поместить const глобальных значений в постоянную память. В C++ значения могут быть инициализированы выражениями, которые не обязательно являются константами времени компиляции, и компилятору разрешено генерировать код во время выполнения для вычисления начального значения. Этот код инициализации вызывается перед входом в main(), аналогично конструкторам для глобальных объектов.

Поскольку вы работаете с постоянными адресами и знаете, насколько велик int на вашей платформе (я предполагаю, что это 32 бита), вы должны просто сделать что-то вроде этого:

Далее, вы используете ключевое слово volatile совершенно неправильно. volatile говорит компилятору не сохранять переменную в регистре - она ​​должна перезагружаться из памяти при каждом чтении и полностью записываться в память при каждой записи. Объявление локальной переменной data_copy как volatile практически бесполезно, если только вы не ожидаете, что другой поток или обработчик сигнала неожиданно начнет изменять ее (крайне сомнительно). Кроме того, data_copy — это просто копия адреса, а не содержимого регистра, который вы пытаетесь прочитать.

Что вы должны сделать, так это объявить REGISTER в качестве указателя на volatile - это одно из явных назначений volatile для доступа к отображаемым в памяти регистрам. Вот как должен выглядеть ваш код:

#define REGISTER (*(volatile type_t *)ADDRESS)
#define DATA (*(const volatile int *)((ADDRESS+4)*2))

Это делает так, что когда вы делаете такие вещи:

REGISTER.first = 1;
REGISTER.first = 2;
int x = REGISTER.second;
int y = DATA;

Он всегда делает правильные вещи: запись 1 в 0x12345678, запись 2 в 0x12345678, чтение из 0x1234567c и чтение из 0x2468acf8. Ключевое слово volatile гарантирует, что эти операции чтения и записи будут происходить всегда, и они не будут оптимизированы: компилятор не удалит первую запись в REGISTER.first, которая была бы избыточной, если бы это была обычная переменная.

ИЗМЕНИТЬ

В ответ на ваш комментарий см. ответ Эндрю Медико на ваш комментарий - вы действительно умножаете разницу между двумя указателями на 2, что нормально. Просто будьте осторожны с вашими типами данных. Я также ничего не упоминал о ядре.

Вы можете заставить gcc помещать переменные в определенный раздел данных с помощью section атрибут:

const volatile int *data __attribute__((section("FLASH")) = /* whatever */;

Используйте правильное название раздела. Если вы не уверены, что это такое, возьмите объектный файл, сгенерированный компилятором C (который, как вы сказали, помещает его в соответствующий раздел), запустите на нем nm и посмотрите, в какой раздел компилятор C поместил его.

person Adam Rosenfield    schedule 16.08.2009
comment
эх... ГОЛЫЙ МЕТАЛЛ чел! Ядра нет, и я действительно ДОЛЖЕН умножать указатели и хранить адрес в любом случае, я знаю, что и зачем я делаю. Взгляните на комментарий к первому ответу. Нестабильная data_copy существует только для того, чтобы компилятор не удалял неиспользуемые данные. Просто придерживайтесь основного вопроса - как разместить умноженный адрес поля структуры во flash. - person Freddie Chopin; 16.08.2009
comment
Явное размещение var в разделе .text не решает проблему. Значение 0 помещается во флэш-память (это объект данных), а функция инициализации все еще пытается скопировать значение из флэш-памяти в... флэш-память... Я знаю, что хочу умножить разницу двух адресов, но это не важно - на самом деле это самый простой тестовый пример, не раздутый другими бесполезными вещами, поэтому, пожалуйста, просто игнорируйте, что глупо умножать указатели, потому что это не главное. - person Freddie Chopin; 16.08.2009

Правильным решением является использование макроса offsetof() из файла stddef.h. заголовок.

В основном это:

const int data = (int)(&REGISTER->second)*2;

должен быть заменен на

#include <stddef.h>
const int data = (int)(REGISTER + offsetof(type_t,second))*2;

А затем объект помещается во Flash как для C, так и для C++.

person Freddie Chopin    schedule 17.08.2009

Вы ознакомились с gcc атрибутами переменных? , возможно, "раздел" хорошо поможет вам в размещении переменной.

person jcopenha    schedule 16.08.2009

  1. Вы не должны умножать указатели.
  2. Вы не должны хранить указатели как «int».

Я уверен, что есть законный способ С++ делать то, что вы хотите, но я не могу понять, что это такое, из кода, который вы опубликовали.

person nobody    schedule 16.08.2009
comment
Поверьте мне, умножение указателей — это именно то, что я хочу сделать. Не возражайте хранить это как int, здесь не в этом дело. Помните — я использую голое железо ARM. В адресном пространстве есть периферийная область и псевдоним битовой полосы этой области. Любой бит в периферийной области отображается на один uin32_t в области битового диапазона. Формула для вычисления адреса в битовой области такова: (0x42000000+(((беззнаковый длинный)адрес)-0x40000000)*32+(бит)*4) Поскольку регистры для STM32 в заголовке организованы в структуры I столкнуться с описанной выше проблемой. - person Freddie Chopin; 16.08.2009
comment
Это выражение на самом деле не умножает указатели — оно умножает разницу между двумя адресами (что является простым числом). Я бы предложил переписать его как-то вроде (uint32_t*)0x42000000 + (addr - (uint32_t*)0x40000000)*32 + бит*4. - person nobody; 16.08.2009
comment
Re.2.: Да, но я постарался предоставить максимально простой тестовый пример. Переписывание макроса так, как вы предлагаете, ничего не меняет, так как каким-то образом компилятор C знает адрес поля, а C++ думает, что не знает этот адрес... Это действительно странно [; Как я писал в вопросе - когда я использую простой адрес - все нормально, проблемы начинаются, когда я хочу использовать адрес поля структуры. - person Freddie Chopin; 16.08.2009
comment
Фредди Шопен: В качестве общего совета: в будущем, если вы задаете вопрос и знаете, что делаете хитрые вещи, но знаете, что делаете и почему, обязательно упоминайте об этом в вопросе, иначе люди... правильно - определите это как проблему. - person Tamas Czinege; 16.08.2009

ИЗМЕНИТЬ:

Вы говорите, что хотите умножить указатели, но это, скорее всего, неправильно. Имейте в виду: (int)(REGISTER) * 2 будет равно (int)(0x12345678 * 2), что равно 0x2468ACF0, вероятно, не тому, что вам нужно. Вы, вероятно, хотите: REGISTER + sizeof(int) * 2, который представляет собой 2 целых числа после последнего байта структуры.

Исходный ответ:

Это похоже на неудачную попытку "взламывать структуру. " (В примере используется стиль c99, но он отлично работает и в C89, просто в качестве последнего элемента должен быть массив из 1). Вероятно, вы хотите что-то вроде этого:

typedef struct {
    int first;
    int second;
    int third;
    unsigned char data[1]; /* we ignore the length really */
} type_t;

type_t *p = (type_t *)0x12345678;

p->data[9]; /* legal if you **know** that there is at least 10 bytes under where data is */

Обычно это используется для структур, выделенных malloc, например:

type_t *p = malloc((sizeof(int) * 3) + 20) /* allocate for the struct with 20 bytes for its data portion */
person Evan Teran    schedule 16.08.2009
comment
Ре. РЕДАКТИРОВАТЬ: значение, которое я ожидаю от кода, опубликованного в вопросе, равно 0x2468acf8. - person Freddie Chopin; 16.08.2009

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

#define UART_BASE_REG ((volatile uint32_t*)0x12341234)
#define UART_STATUS_REG (UART_BASE_REG + 1)

...

person ThePosey    schedule 16.08.2009
comment
Тем не менее, вы не задаете главный вопрос. Я знаю, что вы написали, но есть проблема - шапка со всеми регистрами уже создана, а редактировать 600кБ текста очень не хочется... - person Freddie Chopin; 16.08.2009

Если я правильно понимаю, ваша общая цель резюмируется в этом абзаце:

Мне нужны такие операции для создания константных массивов адресов битов в битовой области Cortex-M3. Это ошибка или, может быть, это какое-то странное ограничение компилятора C++?

Я использую компилятор Keil для STM32, и один из примеров, который идет с ним, содержит макросы для установки и очистки битовых битов:

#define RAM_BASE       0x20000000
#define RAM_BB_BASE    0x22000000

#define  Var_ResetBit_BB(VarAddr, BitNumber)    \
 (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 0)

#define Var_SetBit_BB(VarAddr, BitNumber)       \
 (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 1)

#define Var_GetBit_BB(VarAddr, BitNumber)       \
 (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)))

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

person Steve Melnikoff    schedule 17.08.2009
comment
Макросы здесь не проблема, так как я знаю формулы. Проблема в том, что C++ каким-то образом не хочет хранить такие адреса во Flash, когда ваш VarAddr будет похож на (®ISTER->FIELD). Использование макроса offsetof(), как я писал в одном из ответов, решает здесь настоящую проблему. - person Freddie Chopin; 17.08.2009