如何使两个相同的指针类型不兼容

Jub*_*ian 8 c types pointers avr-gcc harvard-architecture

在某些体系结构上,可能需要为其他相同的对象提供不同的指针类型.特别是对于哈佛架构CPU,您可能需要以下内容:

uint8_t const ram* data1;
uint8_t const rom* data2;
Run Code Online (Sandbox Code Playgroud)

特别是这对于PICs的MPLAB C18(现已停产)中ROM/RAM指针的定义如何.它甚至可以定义如下内容:

char const rom* ram* ram strdptr;
Run Code Online (Sandbox Code Playgroud)

这意味着RAM中的指针指向RAM中指向ROM中的字符串的指针(使用ram不是必需的,因为默认情况下,这个编译器在RAM中,为了清楚起见,所有内容都添加了).

这种语法的好处是,当您尝试以不兼容的方式分配时,编译器能够提醒您,例如ROM位置的地址到指向RAM的指针(如此类似data1 = data2;,或者将ROM指针传递给函数)使用RAM指针会产生错误).

与此相反,在AVR-8的avr-gcc中,没有这种类型的安全性,因为它提供了访问ROM数据的功能.无法区分RAM指针和指向ROM的指针.

在某些情况下,这种类型的安全性对于捕获编程错误非常有益.

有没有办法以某种方式向指针添加类似的修饰符(例如通过预处理器,扩展到可以模仿这种行为的东西)来实现此目的?甚至是一些警告不正当访问的东西?(在avr-gcc的情况下,尝试在不使用ROM访问功能的情况下获取值)

Lun*_*din 7

一个技巧是将指针包装在结构中.指向struct的指针比指向原始数据类型的指针具有更好的类型安全性.

typedef struct
{
  uint8_t ptr;
} a_t;

typedef struct
{
  uint8_t ptr;
} b_t;

const volatile a_t* a = (const volatile a_t*)0x1234;
const volatile b_t* b = (const volatile b_t*)0x5678;

a = b; // compiler error
b = a; // compiler error
Run Code Online (Sandbox Code Playgroud)


Jub*_*ian 2

由于我收到了几个答案,它们在提供解决方案方面提供了不同的妥协,因此我决定将它们合并为一个,概述每个答案的优点和缺点。因此您可以选择最适合您的具体情况的

命名地址空间

对于解决这个问题的特定问题,并且只有 AVR-8 微控制器上的 ROM 和 RAM 指针的情况,最合适的解决方案是这样的。

这是针对 C11 的提案,并未纳入最终标准,但是有支持它的 C 编译器,包括用于 8 位 AVR 的 avr-gcc。

相关文档可以在这里访问(在线 GCC 手册的一部分,还包括使用此扩展的其他架构)。与其他解决方案(例如 AVR-8 的 pgmspace.h 中的类函数宏)相比,它是值得推荐的,因为这样,编译器可以进行适当的检查,同时访问 指向的数据仍然清晰简单。

特别是,如果您在从提供某种命名地址空间的编译器(例如 MPLAB C18)移植某些内容时遇到类似的问题,那么这可能是最快、最干净的方法。

上面移植的指针如下所示:

uint8_t const* data1;
uint8_t const __flash* data2;
char const __flash** strdptr;
Run Code Online (Sandbox Code Playgroud)

(如果可能的话,可以使用适当的预处理器定义来简化该过程)

(奥拉夫的原始回答)

结构体封装,里面有指针

此方法旨在通过将指针包装在结构中来加强指针的类型。预期用途是跨接口传递结构本身,编译器可以通过接口对它们执行类型检查。

字节数据的“指针”类型可能如下所示:

typedef struct{
    uint8_t* ptr;
}bytebuffer_ptr;
Run Code Online (Sandbox Code Playgroud)

可以按如下方式访问指向的数据:

bytebuffer_ptr bbuf;
(...)
bbuf.ptr = allocate_bbuf();
(...)
bbuf.ptr[index] = value;
Run Code Online (Sandbox Code Playgroud)

接受此类类型并返回类型的函数原型可能如下所示:

bytebuffer_ptr encode_buffer(bytebuffer_ptr inbuf, size_t len);
Run Code Online (Sandbox Code Playgroud)

(dvhh 的原始答案)

结构体封装,指针在外

与上面的方法类似,它的目的是通过将指针包装在结构中来加强指针的类型,但以不同的方式提供更强大的约束。所指向的数据类型是封装的。

字节数据的“指针”类型可能如下所示:

typedef struct{
    uint8_t val;
}byte_data;
Run Code Online (Sandbox Code Playgroud)

可以按如下方式访问指向的数据:

byte_data* bbuf;
(...)
bbuf = allocate_bbuf();
(...)
bbuf[index].val = value;
Run Code Online (Sandbox Code Playgroud)

接受此类类型并返回类型的函数原型可能如下所示:

byte_data* encode_buffer(byte_data* inbuf, size_t len);
Run Code Online (Sandbox Code Playgroud)

(Lundin 的原始回答)

我应该使用哪个?

在这方面,命名地址空间不需要太多讨论:如果您只想处理目标处理地址空间的特殊性,那么它们是最合适的解决方案。编译器将为您提供所需的编译时检查,您无需尝试进一步发明任何东西。

但是,如果由于其他原因您对结构包装感兴趣,那么您可能需要考虑以下问题:

  • 这两种方法都可以很好地优化:至少 GCC 会从使用普通指针的任何一种方法生成相同的代码。因此,您实际上不必考虑性能:它们应该有效。

  • 如果您有第三方接口来服务哪些需求指针,或者您正在重构一些无法一次性完成的大型内容,则内部指针非常有用。

  • 外部指针提供了更强大的类型安全性,因为您用它来强化指向类型本身:您拥有一个真正不同的类型,您无法轻松(意外地)转换(隐式转换)。

  • 外部指针允许您在指针上使用修饰符,例如添加const,这对于创建健壮的接口非常重要(您可以使数据只能由函数读取const)。

  • 请记住,有些人可能不喜欢其中任何一个,因此,如果您在团队中工作,或者正在创建可能被已知各方重用的代码,请首先与他们讨论此事。

  • 应该是显而易见的,但请记住,假设没有与该方法一起使用命名地址空间,封装并不能解决需要特殊访问代码的问题(例如 AVR-8 上的 pgmspace.h 宏)。它仅提供一种方法,如果您尝试通过在与指针所指向的地址空间不同的地址空间上操作的函数来使用指针,则会产生编译错误。

感谢您的所有回答!