1

As discussed in Access symbols defined in the linker script by application, "Accessing a linker script defined variable from source code is not intuitive" - essentially, accessing their value usually isn't what you want (since they don't really have a block of memory assigned, as a true compiler variable), and only their accessed by their address. Is there an attribute that can be applied to the variable upon declaration, or perhaps a PC-Lint/static-analysis property/rule which can be applied to the variables?

/* Linker config (.icf) file */
define symbol __ICFEDIT_region_ROM_start__  = 0x08000000;
define symbol __ICFEDIT_region_ROM_end__    = 0x080FFFFF;

define symbol __ICFEDIT_region_ROM_size__ = (__ICFEDIT_region_ROM_end__ - __ICFEDIT_region_ROM_start__) + 1;
export symbol __ICFEDIT_region_ROM_start__;
export symbol __ICFEDIT_region_ROM_size__;
/* main.c */
void OS_SetROM(uint32_t start, uint32_t size){} // empty for demonstration only
int main(void)
{
  extern unsigned int __ICFEDIT_region_ROM_start__;
  extern unsigned int __ICFEDIT_region_ROM_size__;

  // INCORRECT - both probably read as '0', depending on what's actually in those locations
  // Can I get a warning or error about this usage?
  OS_SetROM(__ICFEDIT_region_ROM_start__, __ICFEDIT_region_ROM_size__);
  // CORRECT - *addresses of* linker-defined variables read
  OS_SetROM((uint32_t)&__ICFEDIT_region_ROM_start__, (uint32_t)&__ICFEDIT_region_ROM_size__);

It would be nice to have the addresses declared and behave as pointers (as below), i.e. where you can use the value of the pointer variable to represent the address, and 'value-of' semantics make more sense (at least logically - more obvious that you wouldn't dereference in this case), but this isn't how they work - for that, the linker would have to assign a memory location as well and store the address there, or some special semantics of the compiler/linker, which doesn't appear to be possible...

void void OS_SetROM(uint32_t * const start, uint32_t size){} // empty for demonstration only
int main(void)
{
  // would be nice, but not how it works
  extern unsigned int * const __ICFEDIT_region_ROM_start__;
  extern unsigned int const __ICFEDIT_region_ROM_size__;

  OS_SetROM(__ICFEDIT_region_ROM_start__, __ICFEDIT_region_ROM_size__);

A compromise of-sorts could be to redefine these variables with an appropriate type, ala:

unsigned int * const p_rom_start = &__ICFEDIT_region_ROM_start__;
unsigned int const rom_size = (unsigned int)&__ICFEDIT_region_ROM_size__;

void OS_SetROM(unsigned int * const p_start, unsigned int size);

OS_SetROM(p_rom_start, rom_size);

which helps collect the 'unintuitive' accesses into one place and type-safe accesses thereafter, but that isn't possible in this case as the API is predefined to require uint32_t's.

I realize this is probably uncommon (and probably only used a few times within a project, if at all), and I realize this also depends on using the attribute (e.g. when creating a new project), but I'm curious if there are guards that can be put in place to protect against accidental misuse - or against incorrect 'simplification' (e.g. by some maintainer later who doesn't understand the implications)... I also can't think of another scenario where enforcing 'address-only' access makes sense, so the solution may not exist...

johnny
  • 4,024
  • 2
  • 24
  • 38
  • see also https://stackoverflow.com/questions/55622174/is-accessing-the-value-of-a-linker-script-variable-undefined-behavior-in-c & https://stackoverflow.com/questions/48561217/how-to-get-value-of-variable-defined-in-ld-linker-script-from-c – johnny Jul 29 '20 at 21:18

2 Answers2

3

You can declare an identifier to be for an object of incomplete type, such as a structure whose definition is not given:

extern struct NeverDefined __ICFEDIT_region_ROM_start__;

Then you can take its address, but attempting to use or assign it will yield a compiler error.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • This works, and _will_ result in a type error if you don't specifically take the address-of, so I guess it does technically answer the stated question (only allow address-of access to a var), but the other answer is more useful in this specific scenario (as value access may actually be useful, but is not generally preferred). I upvoted of course but will change the question title to be more specific to the content/context. – johnny Jul 29 '20 at 21:24
2

You shouldn't put c++ and c in the same question; they are different languages for different purposes.

In C, at least, declaring them as:

extern char ROMStart[], ROMEnd[];  /* shortforms are less clumbsy */

gives you much of what you want:

OS_SetROM(ROMStart, ROMEnd, ROMEnd-ROMStart); /* not sure why the last one */

although clever by half compilers might complain if you try to treat these locations as shorts or longs about alignment, so to mollify them, you likely would:

extern long ROMStart[], ROMEnd[];
intptr_t size = (intptr_t)ROMEnd - (intptr_t)ROMStart;
OS_SetROM(ROMStart, ROMEnd, size);

In C++ you would have to consult the language standard du minute.

mevets
  • 10,070
  • 1
  • 21
  • 33
  • I've removed the `c++` tag; we do use c++ in embedded, but not specifically here, so makes sense to keep it to `c`; also removed `lint` tag as neither of the two (currently) answers specify any lint usage. – johnny Jul 29 '20 at 21:17
  • I like this answer from a type perspective. Using the array-name-as-ptr feature didn't occur to me, but is clever and helps the type make more sense than 'struct' in the other answer), and it does still 'work' (correctly) even if you leave off the `&`. It is also still _possible_ to get the value at the address with this form (`long start = ROMStart[0]`), but it's obviously not accidental. This is also the recommended form in some (updated) references on accessing linker vars from source. (see linked q's) – johnny Jul 29 '20 at 21:18