只写指针类型

Tho*_*ews 50 c c++ pointers const-correctness readonly

我正在为嵌入式系统编写软件.

我们使用指针访问FPGA器件的寄存器.
一些寄存器是只读的,而另一些寄存器是只写的.

只读寄存器在读取时将产生未定义的值.

我想定义一个指针类型,允许编译器检测何时从只写寄存器(也称为解除引用)读取值.

只使用C语言语法创建只写指针吗?
(我们正在使用C开发第一个原型,但是在第二代转向C++.)

如何在C++中创建高效的只写指针?(请记住,这不是跟踪动态内存中的项目,而是访问硬件地址.)

此代码用于安全性和质量最受关注的嵌入式系统.

Jer*_*fin 60

我可能会为每个写一个小的包装类:

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; } 
};
Run Code Online (Sandbox Code Playgroud)

至少假设你的系统有一个合理的编译器,我希望这两个都得到优化,所以生成的代码与使用原始指针无法区分.用法:

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

y = x + 1;         // No problem

x = y;             // won't compile
Run Code Online (Sandbox Code Playgroud)

  • @JonPurdy:我避免使用`T volatile*',因为这意味着用户有一个指向寄存器的读/写指针 - 正是我们想要避免的.嵌入式编译器通常有点......受限,因此期望它们包含`intptr_t`(刚刚在C++ 11中添加)需要很多.如果有的话,我会把它作为模板参数.我同意`const volatile` - 刚刚编辑过; 谢谢. (3认同)
  • +1.由于只写是很难调试的,因此隔离写入函数可以为您提供添加日志记录的好地方.我仍然担心使用int作为地址.我工作的最后一个嵌入式系统有16位整数但是32位地址.我会为地址创建一个typedef并使用它,因此很容易重新使用其他系统的代码. (2认同)
  • @AdrianMcCarthy:typedef也可以工作,但正如我上面所说,如果我要从int更改它,我可能只是使用模板参数.对于测试,您还可以创建一个存储(并允许检索)最后写入的值的类,因此其余代码可以将其视为读/写内存. (2认同)

Gia*_*sio 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);
Run Code Online (Sandbox Code Playgroud)

使用xor中的READ和WRITE来表达权限.

比在fpga_register.c你定义一个新的结构

typedef struct register_t2 {
    char permissions;
    void * address;
} FPGARegisterReal;
Run Code Online (Sandbox Code Playgroud)

以便您返回指向它的指针而不是指向FPGARegisteron 的指针fpga_init.

然后,打开fpga_readfpga_write检查权限和

  • 如果允许operetion,FPGARegister则将参数从a转换为a FPGARegisterReal,执行所需的操作(设置或读取值)并返回成功代码
  • 如果不允许该操作,只需返回错误代码

这样,包括头文件在内的任何人都不能访问该FPGARegisterReal结构,因此它不能直接访问寄存器地址.显然,人们可以破解它,但我很确定这种故意的黑客行为不是你真正关心的问题.


Mat*_*son 8

我曾经使用过很多硬件,其中一些硬件只有"只读"或"只写"寄存器(或者根据你是否读取或写入寄存器而使用不同的函数,当有人决定这样做时,这会很有趣) reg | = 4;"而不是记住它应该具有的值,设置第2位并写入新值,就像你应该的那样.没有什么比尝试调试具有随机位的硬件而从你无法读取的寄存器中消失的东西!到目前为止,我还没有看到任何实际阻止从只写寄存器读取或写入只读寄存器的尝试.

顺便说一句,我是否说过具有"只写"的寄存器是一个非常糟糕的主意,因为你无法读回来检查软件是否正确设置了寄存器,这使调试变得非常困难 - 以及编写驱动程序的人不喜欢通过两行VHDL或Verilog代码来调试可以变得非常简单的难题.

如果您对寄存器布局有一定的控制权,我建议您将"readonly"寄存器放在4KB对齐的地址上,将"writeonly"寄存器放在另一个4KB对齐的地址中[超过4KB就可以了].然后,您可以编程硬件的内存控制器以防止访问.

或者,如果正在读取不应该读取的寄存器,或者写入不应写入的寄存器,则让硬件产生中断.我认为硬件确实会产生其他用途的中断?

使用各种C++解决方案提出的其他建议很好,但它并没有真正阻止那些有意直接使用寄存器的人,所以如果它真的是一个安全问题(而不是"让它变得尴尬"),那么你应该有硬件,以防止滥用硬件.


Kaz*_*Kaz 7

在C中,您可以使用指向不完整类型的指针来阻止所有解除引用:


/* writeonly.h */
typedef struct writeonly *wo_ptr_t;
Run Code Online (Sandbox Code Playgroud)
/* writeonly.c */
#include "writeonly.h"

struct writeonly {
  int value 
};

/*...*/

   FOO_REGISTER->value = 42;
Run Code Online (Sandbox Code Playgroud)
/* someother.c */
#include "writeonly.h"

/*...*/

   int x = FOO_REGISTER->value; /* error: deref'ing pointer to incomplete type */
Run Code Online (Sandbox Code Playgroud)

只有writeonly.c,或者通常任何具有定义的代码都struct writeonly可以取消引用指针.当然,该代码也可能意外地读取该值,但至少所有其他代码都被阻止将所有指针解除引用,同时能够传递这些指针并将它们存储在变量,数组和结构中.

writeonly.[ch] 可以提供写入值的功能.


Mar*_*erg 6

我没有看到在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
}
Run Code Online (Sandbox Code Playgroud)

这样可以为您提供静态错误检查.当然,使用这种方法,你必须出去修改所有指针声明才能使用宏,这可能不是很有趣.

编辑: 如评论中所述.

#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
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你有一个指针'x`被定义为可以被解引用的指针(`DEREF_PTR(int*,x)`),那么预处理器也会定义一个名为`x_DEREF_PTR`的幕后类型.当调用`DEREFERENCE`时,它在一个单独的范围内实例化这种类型的变量.对于使用`NO_DEREF_PTR`定义的指针,此类型不存在,因此会引发错误. (2认同)