【问题标题】:Should one use Named Address Spaces where they are available?是否应该在可用的地方使用命名地址空间?
【发布时间】:2018-05-14 09:22:52
【问题描述】:

有些架构有多个地址空间,值得注意的例子是真正的哈佛架构,但例如 OpenCL 也有这个属性。

C 编译器可能会为此提供一些解决方案,其中之一是命名地址空间,支持特殊的指针限定符来指示指针的地址空间,但也可能存在其他解决方案。

我不知道它们,并且在我使用的体系结构(8 位 AVR)上,还有另一种解决该问题的解决方案,专门的宏 (pgmspace.h) 来处理 ROM 中的数据。但是对这些没有类型检查,并且它们(在我看来)使代码变得丑陋,所以在我看来,使用命名地址空间是一种更好的,甚至可能更便携的方式来处理问题(在那个方面是便携的)通过为地址空间限定符提供空定义,可以轻松地将此类软件移植到具有单个地址空间的目标。

但是在我从它们的可用性中了解到的上一个问题中,建议使用命名地址空间的解决方案被严重否决,这里:How to make two otherwise identical pointer types incompatible

downvoters 没有提供任何解释,我自己也没有找到任何解释,对我来说,命名地址空间似乎是处理问题的一种很好且功能完善的方法。

谁能解释一下?为什么可能不应该使用命名地址空间? (偏向于具有多个不同地址空间的目标上可用的任何其他方法)

【问题讨论】:

  • 我不是反对者之一,但 C 社区经常坚持可移植性。根据定义,命名地址空间是特定于某些实现的,不能广泛移植。恕我直言,当您需要区分 RAM 与 ROM 时,它仍然是合适的工具。顺便说一句,标准 C 没有 RAM/ROM 的概念......
  • @SergeBallesta 我明白了,但是如果目标有这个限制(没有通用指针),则必须提供一些特定于平台的方法,并且程序员必须使用它。在那个特定的问题上,高度评价的结构既不能解决这部分问题,但可以肯定的是,如果做得好,它们可以成为一种包含它的方法。出于这个原因,我在上面也提到了命名地址空间的可移植性,例如,在 AVR 的情况下,只需在公共标头中添加 #define __flash 即可消除区别。
  • 我认为有几个缺点:a) 最糟糕的,目标依赖。我们正在创建一个更难移植的代码,没有适当的理由。 b) 要创建一个漂亮且定义明确的内存映射,我们可以使用链接器,它对每个人来说更标准且易于理解。 c) 代码规则和编译器。我们将不得不添加大量可能隐藏实际问题的类型转换。出于测试目的,通常使用两个编译器(比如说 AVR 和 Intel)。 d) 您可以使用“#ifdef __flash”,但如果不是-def,您将无法获得所需的内存映射。
  • @JoseFelipe 我认为您并不真正了解命名地址空间在哪里可用。要点是在这样的架构上,它需要不同的二进制代码(不同的指令)来访问不同的地址空间。您不能在指向不同地址空间的指针之间进行类型转换! (编译器会抛出一个错误)它并不是用来控制内存映射的工具(不仅仅是地址空间本身所暗示的)。在 AVR 上,IAR 也具有相同的命名地址空间支持。
  • @Jubatian 正如你所读到的,我说的是几个编译器的使用。如果您的编译器不包含 named-address-spaces 关键字,则必须解决它。取决于编译器而不使用链接器(从我的角度来看这是正确的解决方案),解决方案只能是 ram(const 不能确保使用 flash)。

标签: c pointers memory-address


【解决方案1】:

另一种方法是窃取在 Linux 内核和 smatch 等工具中使用的技术。

Linux 有类似的定义

#define __user

这意味着代码可以说 int foo(const __user char *p) 之类的东西。编译器会忽略 __user,但随后会使用 smatch 等工具来确保指针不会意外地在命名空间之间徘徊。

【讨论】:

  • 我提到可以这样做,但是我不知道静态分析工具!这是一个很棒的发现,也是另一个问题的答案:stackoverflow.com/questions/49733195/…。有点开箱即用,但很合适,因为它可以解决问题。
【解决方案2】:

这些问题很明显:它们只适用于 gcc 编译器。

在嵌入式系统分支中,有许多不同的编译器,每一个都提供了自己独特的、不可移植的方式来执行此操作。有时这很好(大多数嵌入式项目永远不会移植到不同的编译器),但从一般的角度来看,它不是。

(扩展地址也存在同样的问题 - 例如,如果您使用具有超过 64kib 可寻址内存的 8 位或 16 位 MCU。编译器然后使用各种非标准扩展,例如 nearfar .)

解决这些问题的一个方法是围绕编译器特定的行为创建一个“包装器”,方法是创建一个硬件抽象层 (HAL),在其中指定用于在闪存中存储数据的类型为 flash_byte_t 或类似的,然后从您的 HAL 中包含一个特定于编译器的头文件,其中包含实际的 typedef,例如 typedef const __flash uint8_t flash_byte_t;。例如,应用程序包含“compiler.h”,而这个应用程序又包含“gcc.h”。这样切换编译器时只需要重写一个小头文件。

事实证明,C 允许 const flash_byte_t 很好,即使这已经被 typedef 定义为 const。 C 中有一条特殊的奇怪规则,即您可以在声明中多次添加相同的限定符。所以const const int x 等价于const int x。这意味着,如果用户需要额外的 const 限定,那很好。


请注意,这里主要是 AVR 是一个特殊的例外,因为它的哈佛模型很奇怪。

否则,大多数编译器都使用行业事实上的标准约定:所有具有静态存储持续时间的const 限定变量都应分配在闪存中。当然,C 标准对此不做任何保证(它超出了标准的范围),但大多数嵌入式编译器的行为都是如此。

【讨论】:

  • 不只是 GCC,通过一些研究,我发现特别是对于 8 位 AVR,GCC 实际上遵循了 IAR,而 IAR 的使用时间更长。您的解决方案实际上需要命名地址空间才能工作(至少在 AVR 上,另一个 pgmspace.h 替代方案对此并没有真正有用),因为我认为您打算提供行为正常的数组或指针(您可以在哪里做 @ 987654330@)。可以很好地隐藏命名地址空间以实现可移植性,我提到使用定义是最快速和最脏的端口,但当然没有什么能阻止人们使用更好的类型更进一步。
猜你喜欢
  • 2021-03-12
  • 2019-11-06
  • 1970-01-01
  • 1970-01-01
  • 2013-12-25
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 1970-01-01
相关资源
最近更新 更多