Тип указателя только для записи

Я пишу программное обеспечение для встроенной системы.

Мы используем указатели для доступа к регистрам устройства FPGA.
Некоторые из регистров доступны только для чтения, а другие только для записи.

Регистры только для записи будут давать неопределенные значения при чтении.

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

Можно ли создать указатель только для записи, используя только синтаксис языка Си?
(Мы разрабатываем первый прототип с использованием C, но переходим на C ++ во 2-м поколении.)

Как создать эффективный указатель только для записи в C ++?
(Помните, что это не отслеживание элементов в динамической памяти, а доступ к аппаратным адресам.)

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

52

Решение

Я, вероятно, написал бы крошечный класс-обертку для каждого:

template <class T>
class read_only {
T volatile *addr;
public:
read_only(int address) : addr((T *)address) {}
operator T() volatile const { return *addr; }
};

template <class T>
class write_only {
T volatile *addr;
public:
write_only(int address) : addr ((T *)address) {}

// chaining not allowed since it's write only.
void operator=(T const &t) volatile { *addr = t; }
};

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

read_only<unsigned char> x(0x1234);
write_only<unsigned char> y(0x1235);

y = x + 1;         // No problem

x = y;             // won't compile
61

Другие решения

Я работал с большим количеством аппаратного обеспечения, и некоторые из них имеют регистры «только для чтения» или «только для записи» (или различные функции в зависимости от того, читаете ли вы или записываете в регистр, что очень интересно, когда кто-то решит это сделать » reg | = 4; «вместо того, чтобы запоминать значение, которое оно должно иметь, установите бит 2 и запишите новое значение, как вы должны. Ничто не сравнится с попыткой отладки оборудования, в котором случайные биты появляются и исчезают из регистров, которые вы не можете прочитать!; Я до сих пор не видел никаких попыток фактически заблокировать чтение из регистра только для записи или записи в регистры только для чтения.

Кстати, я говорил, что иметь регистры, которые являются «только для записи», ДЕЙСТВИТЕЛЬНО плохая идея, потому что вы не можете прочитать обратно, чтобы проверить, правильно ли программа установила регистр, что делает отладку действительно сложной — и люди пишут драйверы не люблю отлаживать сложные проблемы, которые могут быть действительно легко решены с помощью двух строк VHDL или кода Verilog.

Если у вас есть некоторый контроль над компоновкой регистров, я бы предложил поместить регистры «только для чтения» по адресу, выровненному по 4 КБ, а регистры «только для записи» по другому адресу, выровненному по 4 КБ [более 4 КБ — это нормально]. Затем вы можете запрограммировать аппаратный контроллер памяти для предотвращения доступа.

Или, пусть аппаратное обеспечение создает прерывание, если регистры, которые не должны быть прочитаны, читаются, или регистры, которые не должны быть записаны, записываются. Я предполагаю, что оборудование производит прерывания для других целей?

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

8

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

В fpga_register.h у вас было бы что-то вроде

#define FPGA_READ = 1;
#define FPGA_WRITE = 2;
typedef struct register_t {
char permissions;
} FPGARegister;

FPGARegister* fpga_init(void* address, char permissions);

int fpga_write(FPGARegister* register, void* value);

int fpga_read(FPGARegister* register, void* value);

с READ и WRITE в XOR, чтобы выразить разрешения.

Чем в fpga_register.c вы бы определили новую структуру

typedef struct register_t2 {
char permissions;
void * address;
} FPGARegisterReal;

так что вы возвращаете указатель на него вместо указателя на FPGARegister на fpga_init,

Затем на fpga_read а также fpga_write вы проверяете разрешения и

  • если операция разрешена, отбросьте FPGARegister от аргумента до FPGARegisterRealвыполнить желаемое действие (установить или прочитать значение) и вернуть код успеха
  • если операция не разрешена, просто верните код ошибки

Таким образом, никто, включая заголовочный файл, не сможет получить доступ к FPGARegisterReal структура, и, следовательно, он не будет иметь прямой доступ к адресу регистра. Очевидно, что это можно взломать, но я совершенно уверен, что такие преднамеренные взломы не являются вашими настоящими заботами.

8

В C вы можете использовать указатели на неполные типы для предотвращения разыменования:


/* writeonly.h */
typedef struct writeonly *wo_ptr_t;

/* writeonly.c */
#include "writeonly.h"
struct writeonly {
int value
};

/*...*/

FOO_REGISTER->value = 42;

/* someother.c */
#include "writeonly.h"
/*...*/

int x = FOO_REGISTER->value; /* error: deref'ing pointer to incomplete type */

Только writeonly.cили вообще любой код, который имеет определение struct writeonly, может разыменовать указатель. Этот код, конечно, может также случайно прочитать значение, но, по крайней мере, весь другой код не может разыменовать указатели вместе, в то же время он может передавать эти указатели и сохранять их в переменных, массивах и структурах.

writeonly.[ch] может предоставить функцию для записи значения.

7

Я не вижу элегантного способа сделать это в C. Однако я вижу способ сделать это:

#define DEREF_PTR(type, ptr) type ptr; \
typedef char ptr ## _DEREF_PTR;

#define NO_DEREF_PTR(type, ptr) type ptr; \

#define DEREFERENCE(ptr) \
*ptr; \
{ptr ## _DEREF_PTR \
attempt_to_dereference_pointer_ ## ptr;}

int main(int argc, char *argv[]) {
DEREF_PTR(int*, x)
NO_DEREF_PTR(int*, y);

DEREFERENCE(x);
DEREFERENCE(y); // will throw an error
}

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

Редактировать:
Как описано в комментариях.

#define READABLE_PTR(type, ptr) type ptr; \
typedef char ptr ## _READABLE_PTR;

#define NON_READABLE_PTR(type, ptr) type ptr; \

#define GET(ptr) \
*ptr; \
{ptr ## _READABLE_PTR \
attempt_to_dereference_non_readable_pointer_ ## ptr;}

#define SET(ptr, value) \
*ptr = value;int main(int argc, char *argv[]) {
READABLE_PTR(int*, x)
NON_READABLE_PTR(int*, y);

SET(x, 1);
SET(y, 1);

int foo = GET(x);
int bar = GET(y); // error
}
6
По вопросам рекламы [email protected]