0

I've been reading through a lot of answers, and there are a lot of opinions about this but I wasn't able to find a code that answers my question (I found a lot of code that answers "how to share variables by declaring")

Here's the situation:

  • Working with embedded systems
  • Using IAR workbench systems
  • STM32F4xx HAL drivers
  • Declaring global variables is not an option (Edit: Something to do with keeping the memory size small, so local variables disappear at the end of scope but the global variable stay around. The local variables were sent out as outputs, so we discard them right away as we don't need them)
  • C language
  • in case this is important: 2 .c files, and 1 .h is included in both

Now that's out of the way, let me write an example.

file1.c - Monitoring

void function(){
    uint8_t varFlag[10]; // 10 devices
    for (uint8_t i = 0; i < 10; i++)
    {
        while (timeout <= 0){
            varFlag[i] = 1;
            // wait for response. We'll know by the ack() function
            // if response back from RX, 
            // then varFlag[i] = 0;
    }

file2.c - RX side

// listening... once indicated, this function is called
// ack is not called in function(), it is called when 
// there's notification that there is a received message
// (otherwise I would be able to use a pointer to change 
// the value of varFlag[]
void ack(uint8_t indexDevice)
{
    // indexDevice = which device was acknowledged? we have 10 devices
    // goal here is to somehow do varFlag[indexDevice] = 0
    // where varFlag[] is declared in the function()

}
Smiley
  • 75
  • 1
  • 11
  • 1
    Please explain why having a global variable is not an option, so **edit your question** to improve it and motivate it. – Basile Starynkevitch Mar 17 '16 at 13:31
  • 1
    Even with your pseudo code, I don't understand exactly what you want to share. So please **improve your question**; show exactly in the code (and mention explicitly in your question) the actual "variable" you want to share. – Basile Starynkevitch Mar 17 '16 at 13:34
  • 1
    I'm tempted to downvote or ask this question to be closed, since unclear. So please improve your question by editing it! – Basile Starynkevitch Mar 17 '16 at 13:38
  • I'm still in the middle of editing, so give me time. – Smiley Mar 17 '16 at 13:42
  • BTW, giving some more context (what kind & size of application, what kind of embedded system, what compiler, what compilation flags) would be helpful... – Basile Starynkevitch Mar 17 '16 at 13:44
  • We still don't know what processor or ISA you are targetting – Basile Starynkevitch Mar 17 '16 at 14:12
  • 2
    I can smell lots of potential design issues here. Most likely this would all sort itself out naturally if the original program design was sound. As in: caller -> HAL -> driver, where the HAL is optional and where the driver is typically just one single module (h + c file) per piece of hardware. If you have a different design, then that might be the root cause of the problem. – Lundin Mar 17 '16 at 14:14
  • 1
    I finally downvoted and asked for closing the question. It is unclear, and you look really confused. Perhaps take time to read some good C programming book, and practice some C programming (e.g. compile a small program, run it step by step in the debugger) on your laptop... – Basile Starynkevitch Mar 17 '16 at 14:25
  • 3
    Btw why aren't you using a `uint16_t` as a bit-field, instead of using 10 bytes? If memory is such a tight issue, then it should be noted that 2 bytes are less than 10 bytes. Bitwise operators are also faster than array indexing. – Lundin Mar 17 '16 at 14:26
  • 2
    @BasileStarynkevitch I kind of agree, but in this case it might also be the lead dev who is confused. It is not uncommon that you have some semi-experienced lead dev who forces all kinds of weird believes on their team. – Lundin Mar 17 '16 at 14:33
  • After a conversation with my lead, we finally agreed to use global variables. All the reasons were mentioned in the comments -- @Lundin has good valid points. – Smiley Mar 17 '16 at 15:40
  • @Lundin, using `uint16_t` as a bit-field is a fantastic idea. I shall do that due to speed and memory space. – Smiley Mar 17 '16 at 15:41
  • @Smiley : Deciding to use global variables was exactly the wrong decision - but not for the reasons you gave in your question - see my answer. Using bit-fields is unlikely to effect an improvement in speed (probably the reverse), unless you happen to be using 8051 bit addressable memory, or ARM Cortex-M bit-banding for example. – Clifford Mar 18 '16 at 14:05

4 Answers4

3

You share values or data, not variables. Stricto sensu, variables do not exist at runtime; only the compiler knows them (at most, with -g, it might put some metadata such as offset & type of locals in the debugging section -which is usually stripped in production code- of the executable). Ther linker symbol table (for global variables) can, and often is, stripped in a embedded released ELF binary. At runtime you have some data segment, and probably a call stack made of call frames (which hold some local variables, i.e. their values, in some slots). At runtime only locations are relevant.

(some embedded processors have severe restrictions on their call stack; other have limited RAM, or scratchpad memory; so it would be helpful to know what actual processor & ISA you are targeting, and have an idea of how much RAM you have)

So have some global variables keeping these shared values (perhaps indirectly thru some pointers and data structures), or pass these values (perhaps indirectly, likewise...) thru arguments.

So if you want to share the ten bytes varFlag[10] array:

  • it looks like you don't want to declare uint8_t varFlag[10]; as a global (or static) variable. Are you sure you really should not (these ten bytes have to sit somewhere, and they do consume some RAM anyway, perhaps in your call stack....)?

  • pass the varFlag (array, decayed to pointer when passed as argument) as an argument, so perhaps declare:

    void ack(uint8_t indexDevice, uint8_t*flags);
    

    and call ack(3,varFlag) from function...

    • or declare a global pointer:

      uint8_t*globflags;
      

and set it (using globflags = varFlag;) at the start of the function declaring varFlag as a local variable, and clear if (using globflags = NULL;) at the end of that function.

I would suggest you to look at the assembler code produced by your compiler (with GCC you might compile with gcc -S -Os -fverbose-asm -fstack-usage ....). I also strongly suggest you to get your code reviewed by a colleague...

PS. Perhaps you should use GCC or Clang/LLVM as a cross-compiler, and perhaps your IAR is actually using such a compiler...

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • I keep seeing this answer (pointers/data structures or passing values), and I wanted to implement it, but I don't see any example codes. Can you please provide me one? – Smiley Mar 17 '16 at 13:33
  • 1
    I could provide an example when you will improve your question (by editing it), as I am asking you... – Basile Starynkevitch Mar 17 '16 at 13:36
  • I know I want to use global variables, but it is one of the restrictions, my team lead was very strict on that. Now I see where you're coming from (where I'm unclear about), lemme edit the question once more. – Smiley Mar 17 '16 at 14:06
  • 1
    The compiler knows about *symbols* - variables definitely exist in compiled code - teh *symbols* don't. Your first paragraph is nonsense and an unnecessary point in any case. – Clifford Mar 18 '16 at 12:46
  • No, a stripped ELF executable do not have any information about local symbols; indeed the compiler knows about symbols, but not the executable produced by the compiler. So I maintain my point; variables do not exist in (embedded, stripped) binary executables (except indeed symbol table related to dynamic linking, which is often not used in free standing binary code). When I upload a binary to my Arduino it has no symbol table. – Basile Starynkevitch Mar 18 '16 at 13:23
  • 1
    @BasileStarynkevitch : Memory locations containing data that change *are variables* - they still exist. The source level symbols do not exist, the variables those symbols represent certainly do exist. You are confusing symbols with variables, and the point is irrelevant to the question or answer in any case, so if anything should be a comment not part of an answer. By the same perverse logic, you could say the code does not exist after compilation! – Clifford Mar 18 '16 at 14:08
  • No, machine code does not contain or refer to variables. Only to memory locations (either offset w.r.t. to stack or frame pointer for local variables, or absolute addresses for global or static variables). And sometimes, a variable has no address (because the compiler put it in a register, or optimized it). BTW, the symbol word is different in Scheme and in ELF executables... – Basile Starynkevitch Mar 18 '16 at 14:11
2

Your argument for not using global variables:

Something to do with keeping the memory size small, so local variables disappear at the end of scope but the global variable stay around. The local variables were sent out as outputs, so we discard them right away as we don't need them

confuses lifetime with scope. Variables with static lifetime occupy memory permanently regardless of scope (or visibility). A variable with global scope happens to also be statically allocated, but then so is any other static variable.

In order to share a variable across contexts it must necessarily be static, so there is no memory saving by avoiding global variables. There are however plenty of other stronger arguments for avoiding global variables and you should read A Pox on Globals by Jack Ganssle.

C supports three-levels of scope:

  • function (inside a function)
  • translation-unit (static linkage, outside a function)
  • global (external linkage)

The second of these allows variable to be directly visible amongst functions in the same source file, while external linkage allows direct visibility between multiple source files. However you want to avoid direct access in most cases since that is the root of the fundamental problem with global variables. You can do this using accessor functions; to use your example you might add a file3.c containing:

#include "file3.h"

static uint8_t varFlag[10]; 
void setFlag( size_t n )
{
    if( n < sizeof(varFlag) )
    {
        varFlag[n] = 1 ;
    }
}

void clrFlag( size_t n )
{
    if( n < sizeof(varFlag) )
    {
        varFlag[n] = 0 ;
    }
}

uint8_t getFlag( size_t n )
{
    return varFlag[n] == 0 ? 0 : 1 ;
}

With an associated header file3.h

#if !defined FILE3_INCLUDE
#define FILE3_INCLUDE

void setFlag( size_t n ) ;
void clrFlag( size_t n ) ;
uint8_t getFlag( size_t n ) ;

#endif

which file1.c and file2.c include so they can access varFlag[] via the accessor functions. The benefits include:

  • varFlag[] is not directly accessible
  • the functions can enforce valid values
  • in a debugger you can set a breakpoint catch specifically set, clear or read access form anywhere in the code.
  • the internal data representation is hidden

Critically the avoidance of a global variable does not save you memory - the data is still statically allocated - because you cannot get something for nothing varFlag[] has to exist, even if it is not visible. That said, the last point about internal representation does provide a potential for storage efficiency, because you could change your flag representation from uint8_t to single bit-flags without having to change interface to the data or the accessing the accessing code:

#include <limits.h>
#include "file3.h"

static uint16_t varFlags ;

void setFlag( size_t n )
{
    if( n < sizeof(varFlags) * CHAR_BIT )
    {
        varFlags |= 0x0001 << n ;
    }
}

void clrFlag( size_t n )
{
    if( n < sizeof(varFlags) * CHAR_BIT )
    {
        varFlags &= ~(0x0001 << n) ;
    }
}

uint8_t getFlag( size_t n )
{
    return (varFlags & (0x0001 << n)) == 0 ? 0 : 1 ;
}

There are further opportunities to produce robust code, for example you might make only the read accessor (getter) publicly visible and hide the so that all but one translation unit has read-only access.

Clifford
  • 88,407
  • 13
  • 85
  • 165
1

Put the functions into a seperate translation unit and use a static variable:

static type var_to_share = ...;

void function() {
    ...
}

void ack() {
    ...
}

Note that I said translation unit, not file. You can do some #include magic (in the cleanest way possible) to keep both function definitions apart.

Community
  • 1
  • 1
cadaniluk
  • 15,027
  • 2
  • 39
  • 67
  • by doing so, we're still using global variable – Smiley Mar 17 '16 at 13:54
  • 1
    @Smiley No, it has internal linkage and is local to the translation unit. Everything outside of the translation unit will not have access. – cadaniluk Mar 17 '16 at 13:55
  • I have to look up what you mean by translation unit. Thanks – Smiley Mar 17 '16 at 13:56
  • 3
    @Smiley Global variables are variables that are accessibly globally, all over the project. `static` variables at file scope are only accessible inside the same translation unit (roughly means same file). The method suggested in this answer is plain everyday embedded programming. It assumes a single-core system though. – Lundin Mar 17 '16 at 14:09
  • 3
    It's true that this static variable does not have global scope. But this static variable uses some memory space permanently and is not located on the stack. If the reason for not using a global variable is because of the memory usage then this doesn't seem like a solution. (However, that seems like a silly reason for not using a global/static variable. And if I understand what the questioner is trying to do correctly then I think this static variable is the way to go.) – kkrambo Mar 17 '16 at 14:10
  • 3
    @kkrambo Dogmatically avoiding static storage duration variables in order to save memory is muddled thinking. A variable either needs to be persistent throughout the whole execution of the program or it doesn't. If it needs to be persistent, it will take up the very same amount of memory no matter if allocated in `.data` or on the stack. If the variable does not need to be persistent, then a plain getter function in the file producing the variable would solve everything. There's not a lot of important variables in embedded systems that do not need to be persistent. – Lundin Mar 17 '16 at 14:17
  • 1
    I agree with you Lundin. Smiley, you should go discuss this with your team lead. "Something to do with keeping the memory size small" is not enough information to be basing your software design on. – kkrambo Mar 17 '16 at 14:20
0

Unfortunately you can't in C.

The only way to do such thing is with assemply.