1

Lets say i have a lookuptable, an array of 256 elements defined and declared in a header named lut.h. The array will be accessed multiple times in lifetime of the program.

From my understanding if its defined & declared as static, it will remain in memory until the program is done, i.e. if it is a task running on a uC, the array is in memory the entire time.

Where as without static, it will be loaded into memory when accessed.

In lut.h

    static const float array[256] = {1.342, 14.21, 42.312, ...}

vs.

    const float array[256] = {1.342, 14.21, 42.312, ...}

Considering the uC has limited spiflash and psram, what would be the most performance oriented approach?

p_d
  • 101
  • 1
  • 12
  • 2
    Maybe [this](https://stackoverflow.com/questions/93039/where-are-static-variables-stored-in-c-and-c) will be of help. :) – Duck Dodgers Feb 05 '19 at 10:42
  • 2
    *Performance oriented* can have different meanings. It could be you want to save on *RAM* or you have limited *ROM* or the response of whatever device you are controlling with the uC needs to be fast. – Duck Dodgers Feb 05 '19 at 10:51
  • Also [this](https://electronics.stackexchange.com/questions/34096/where-are-static-variables-stored) is worth looking at. – Duck Dodgers Feb 05 '19 at 10:53
  • 2
    You're talking about 'declaration' but you're showing 'definition'. The array should be declared in a header file (in case you need to access it in multiple source files). Then the array should be defined in a source (`lut.c` for example). See [this](https://stackoverflow.com/a/4645722/1971013). – meaning-matters Feb 05 '19 at 10:54
  • 1
    *"Where as without static, it will be loaded into memory when accessed"* - no, these two will usually provide exactly the same assembly. The only difference is that the static version has a limited scope. – vgru Feb 05 '19 at 11:05
  • 1
    Nitpick: float constants should be written as `1.342f`. Otherwise you might trigger the tool chain to link in a higher accuracy floating point library that you have no use for. – Lundin Feb 05 '19 at 12:15

2 Answers2

4

You have some misconceptions here, since a MCU is not a PC. Everything in memory in a MCU will persist for as long as the MCU has power. Programs do not end or return resources to a hosting OS.

"Tasks" on a MCU means you have a RTOS. They use their own stack and that's a topic of it's own, quite unrelated to your question. It is normal that all tasks on a RTOS execute forever, rather than getting allocated/deallocated in run-time like processes in a PC.

static versus automatic on local scope does mean different RAM memory use, but not necessarily more/less memory use. Local variables get pushed/popped on the stack as the program executes. static ones sit on their designated address.


Where as without static, it will be loaded into memory when accessed.

Only if the array you are loading into is declared locally. That is:

void func (void)
{
  int my_local_array[] = {1,2,3};
  ...
}

Here my_local_array will load the values from flash to RAM during execution of that function only. This means two things:

  • The actual copy down from flash to RAM is slow. First of all, copying something is always slow, regardless of the situation. But in the specific case of copying from RAM to flash, it might be extra slow, depending on MCU.

    It will be extra slow on high end MCUs with flash wait states that fail to utilize data cache for the copy. It will be extra slow on weird Harvard architecture MCUs that can't address data directly. Etc.

    So naturally if you do this copy down each time a function is called, instead of just once, your program will turn much slower.

  • Large local objects lead to a need for higher stack size. The stack must be large enough to deal with the worst-case scenario. If you have large local objects, the stack size will need to be set much higher to prevent stack overflows. Meaning this can actually lead to less effective memory use.

So it isn't trivial to tell if you save or lose memory by making an object local.

General good practice design in embedded systems programming is to not allocate large objects on the stack, as they make stack handling much more detailed and the potential for stack overflow increases. Such objects should be declared as static, at file scope. Particularly if speed is important.


static const float array vs const float array

Another misconception here. Making something const in MCU system, while at the same time placing it at file scope ("global"), most likely means that the variable will end up in flash ROM, not in RAM. Regardless of static.

This is most of the time preferred, since in general RAM is a more valuable resource than flash. The role static plays here is merely good program design, as it limits access to the variable to the local translation unit, rather than cluttering up the global namespace.


In lut.h

You should never define variables in header files.

It is bad from a program design point-of-view, as you expose the variable all over the place ("spaghetti programming") and it is bad from a linker point of view, if multiple source files include the same header file - which is extremely likely.

Correctly designed programs places the variable in the .c file and limits access by declaring it static. Access from the outside, if needed, is done through setters/getters.


he uC has limited spiflash

What is "spiflash"? An external serial flash memory accessed through SPI? Then none of this makes sense, since such flash memory isn't memory-mapped and typically the compiler can't utilize it. Access to such memories has to be carried out by your application, manually.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Appreciate the lengthy answer. That cleared up quite abit. The uC has ~450kb ROM & 520kb SRAM internally. – p_d Feb 05 '19 at 12:54
  • @p_d So it's gonna be a high end one. Wait states and cache are definitely things to consider then. – Lundin Feb 05 '19 at 13:05
3

If your arrays are defined on a file level (you mentioned lut.h), and both have const qualifiers, they will not be loaded into RAM¹. The static keyword only limits the scope of the array, it doesn't change its lifetime in any way. If you check the assembly for your code, you will see that both arrays look exactly the same when compiled:

static const int static_array[] = { 1, 2, 3 };
const int extern_array[] = { 1, 2, 3};

extern void do_something(const int * a);
int main(void)
{
    do_something(static_array);
    do_something(extern_array);
    return 0;
}

Resulting assembly:

main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:static_array
        call    do_something
        mov     edi, OFFSET FLAT:extern_array
        call    do_something
        xor     eax, eax
        add     rsp, 8
        ret

extern_array:
        .long   1
        .long   2
        .long   3

static_array:
        .long   1
        .long   2
        .long   3

On the other hand, if if you declare the arrays inside a function, then the array will be copied to temporary storage (stack) for the duration of the function, unless you add the static qualifier:

extern void do_something(const int * a);
int main(void)
{
    static const int static_local_array[] = { 1, 2, 3 };
    const int local_array[] = { 1, 2, 3 };

    do_something(static_local_array);
    do_something(local_array);

    return 0;
}

Resulting assembly:

main:
        sub     rsp, 24
        mov     edi, OFFSET FLAT:static_local_array
        movabs  rax, 8589934593
        mov     QWORD PTR [rsp+4], rax
        mov     DWORD PTR [rsp+12], 3
        call    do_something
        lea     rdi, [rsp+4]
        call    do_something
        xor     eax, eax
        add     rsp, 24
        ret

static_local_array:
        .long   1
        .long   2
        .long   3

¹ More precisely, it depends on the compiler. Some compilers will need additional custom attributes to define exactly where you want to store the data. Some compilers will try to place the array into RAM when there is enough spare space, to allow faster reading.

vgru
  • 49,838
  • 16
  • 120
  • 201
  • thanks for making it clear. considering i want the array to be readable from different files & threads const qualifier without static would be sufficient. Does it matter if i split declaration/ definition&initialization into lut.h/lut.c or keep everything in lut.h? – p_d Feb 05 '19 at 11:28
  • *"both have const qualifiers, they will not be loaded into RAM"* I think this depends on compiler and underlying architecture. If I recall correctly some require special keywords for data to not be placed in RAM. – user694733 Feb 05 '19 at 11:28
  • 1
    @P.Dias: yes, if it's a shared lookup table, your `lut.h` should just contain `extern const float array[256];` and the actual const array should reside in `lut.c`. If you place the actual const array in the header, you need to make it `static` or you will get "duplicate symbol" compile-time errors. – vgru Feb 05 '19 at 11:30
  • @user694733: that's correct, I wanted to keep it simple, but I'll clarify. – vgru Feb 05 '19 at 11:43
  • @Groo thanks. Out of curiousity (haven't been programming for too long) what would happen if it's a shared lut and i do not declare the array as extern in lut.h? – p_d Feb 05 '19 at 11:47
  • That's the second case I mentioned in my comment: each header file is always literally copied into any .c file which includes it during preprocessing. This means that you get a copy of the array in each .c file. If the array is marked `static`, compiler won't care, each .c file will get its own private array and the resulting hex will probably be larger. If the array isn't marked `static`, then this means you want to make it visible to other parts of the program, but since a copy exists in multiple compilation units you will get a compilation error (duplicate symbol). – vgru Feb 05 '19 at 11:57
  • Ahh, gotcha. So since memory is limited i'll probably want to declare it extern in lut.h and have a separate lut.c for the actual array. Cheers :) – p_d Feb 05 '19 at 12:08
  • @P.Dias: yes, although a potentially better alternative, in terms of program design, is to have a static lut inside a single .c file and all related functions there, because the rest of your program probably doesn't need to know about this array. Similar to how you can include `` and use `sin(x)`, without caring how it works under the hood. It's generally better to encapsulate as much as possible (i.e. keep all data, functions and variables private to the module unless you really need to expose them). – vgru Feb 05 '19 at 12:20