5

There are some architectures which have multiple address spaces, notable examples are true Harvard ones, but for example OpenCL also has this property.

C compilers may provide some solutions to this, one of these is Named Address Spaces, supporting special pointer qualifiers to indicate the pointer's address space, but other solutions might also be present.

I didn't know about them, and on the architecture I am using (8 bit AVR), there is another solution to deal with the problem, specialized macros (pgmspace.h) to work with data in the ROM. But there are no type checks on these, and they (in my opinion) make code ugly, so it would seem to me that using Named Address Spaces are a superior, and possibly even more portable way to deal with the problem (portable in that one could easily port such a software to a target having a single address space by providing empty definitions for the address space qualifiers).

However in a previous question in which I learned from their availability, solutions suggesting the use of Named Address Spaces got severely downvoted, here: How to make two otherwise identical pointer types incompatible

Downvoters didn't provide any explanation, and I neither found any myself, for me it seems like Named Address Spaces are a good and perfectly functional way of dealing with the problem.

Could anyone provide an explanation? Why Named Address Spaces probably shouldn't be used? (favoring whatever other method available on the target having multiple distinct address spaces)

Jubatian
  • 2,171
  • 16
  • 22
  • I wasn't one of the downvoters, but C community often insist in portability. By definition, Named Address Spaces are specific for some implementations and cannot be widely portable. IMHO, it is still the appropriate tools when you need to do a RAM vs ROM difference. BTW, standard C has no notion of RAM/ROM... – Serge Ballesta May 14 '18 at 09:52
  • @SergeBallesta I see that, but if the target has that limitation (no generic pointers), some platform-specific method has to be provided, and the programmer necessarily has to use it. On that particular question, the highly upvoded structure wraps neither solve this part of the problem, but sure, they can be a method to contain it, if done right. I mentioned portability above for Named Address Spaces too for this reason, it is just the matter of for example `#define __flash` in a common header for the case of an AVR to remove the distinction. – Jubatian May 14 '18 at 09:56
  • I think there are several disadvanteges: a) The worst, target dependence. We are creating a harder portable code,without a proper justification. b) To create a nice and well defined memory map, we can use the linker, which is more standard and easy to understand for everybody. c) Code rules and compilers. We will have to add loads of type-castings which could hide real problems. For testing purposes, it is common to use two compilers (lets say AVR and Intel). d) You could use an "#ifdef __flash" but if not-def, you won't get the desired memory map. – Jose May 14 '18 at 10:29
  • @JoseFelipe I think you don't really understand what Named Address Spaces are for where they are available. The main point is that on such architectures, it requires different binary code (different instructions) to access the distinct address spaces. You can not type cast between pointers pointing to different address spaces! (The compiler would throw an error) It is not meant to be a tool for controlling the memory map (any more than what the address spaces themselves imply). On an AVR, IAR also has the same Named Address Space support. – Jubatian May 14 '18 at 10:47
  • @Jubatian As you have read, I was talking about the use of several compilers. If your compiler doesnt include the named-address-spaces keyword, you will have to solve it. Depending on the compiler and not using the linker (which from my point of view is the proper solution), the solution can only be the ram (const doesn't ensure the use of flash). – Jose May 14 '18 at 11:06
  • @JoseFelipe This is still all nonsense to me. For unit-testing code on a PC, in case it was an AVR with the `__flash` address space, a `#define __flash` in a common header can make the code compile fine for the unit test, and for such different platform tests, memory layout is irrelevant anyway. For the target, using two different C compilers won't help in any way as they generate different binary: if you were concerned about compiler correctness, you would have to unit-test with the compiled modules running in a simulator. – Jubatian May 17 '18 at 06:33

2 Answers2

3

Another approach is to steal a technique used in things like the Linux kernel and tools like smatch.

Linux has defines like

#define __user

which mean the code can say things like int foo(const __user char *p). The compiler ignores the __user but tools like smatch are then used to make sure that pointers don't accidentally wander between namespaces.

Alan Cox
  • 198
  • 6
  • I mentioned that this can be done, however I didn't know about the static analysis tool! That's a great find, also an answer to the other question by which this popped up: https://stackoverflow.com/questions/49733195/how-to-make-two-otherwise-identical-pointer-types-incompatible . A bit out-of-the-box, but fits as it can solve the problem. – Jubatian May 31 '18 at 04:13
1

The problem with these is obvious: they only work on the gcc compiler.

And in the embedded systems branch there are lots of different compilers, each offering its own unique, non-portable way to do this. Sometimes that is fine (most embedded projects never get ported to different compilers) but from a generic point-of-view, it is not.

(The very same issue also exists with extended addresses - if you would for example use a 8 or 16 bit MCU with more than 64kib addressable memory. Compilers then use various non-standard extensions such as near and far.)

One solution to these problems is to make a "wrapper" around the compiler-specific behavior, by making a hardware abstraction layer (HAL) where you specify that the type used for storing data in flash is flash_byte_t or some such, then from your HAL include a compiler-specific header-file containing the actual typedef, such as typedef const __flash uint8_t flash_byte_t;. For example, the application includes "compiler.h" and this one in turn includes "gcc.h". That way you only need to re-write one small header file when you switch compiler.

Also as it turns out, C allows const flash_byte_t just fine even though this was already typedef'd as const. There's a special odd rule in C saying that you can add the same qualifier as many times in a declaration as you like. So const const int x is equivalent to const int x. This means that if the user would put on extra const-qualification, that's fine.


Note that it's mostly AVR being a special exception here, because of its weird Harvard model.

Otherwise, there's an industry de facto standard convention used by most compilers: all const qualified variables with static storage duration should be allocated in flash. Of course the C standard makes no guarantees of this (it is out of scope of the standard), but most embedded compilers behave like that.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • It isn't only GCC, with a little research, I found that for the 8 bit AVR in particular, GCC actually followed IAR which has the same since longer. Your solution actually requires Named Address Spaces to work (at least on the AVR, the other `pgmspace.h` alternative isn't really useful for this) as I presume you meant to provide arrays or pointers behaving the usual way (where you can do `val = array[index];`). One can hide Named Address Spaces well for portability, I mentioned using defines as the most quick-and-dirty port, but of course nothing stops one from going further with better types. – Jubatian May 14 '18 at 12:03