johnny
johnny

Reputation: 4154

How to encourage 'address-of' access to a linker variable

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...

Upvotes: 1

Views: 919

Answers (2)

Eric Postpischil
Eric Postpischil

Reputation: 222669

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.

Upvotes: 3

mevets
mevets

Reputation: 10445

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.

Upvotes: 2

Related Questions