What is a good solution to prevent duplicate variables?Started by 6 years ago●11 replies●latest reply 6 years ago●113 views
Our language is embedded 'C'.
We have global static unsigned int variable called vehicle_speed. A lower level function is called to fetch vehicle speed and assign to this variable.
We are adding a new software module that also needs vehicle speed. In a new file, I can create another global static unsigned int variable called vehicle_speed_motor2. Then, I call same low level function to fetch vehicle speed and assign it to vehicle_speed_motor2.
To avoid Two variables, I can create global variable but we think that would be too visible.
Are there any other solutions?
"Global static"? That doesn't make sense. The variable can be either global (accessible by all modules) or static (accessible by the module it resides in). It can't be both. I assume you mean static.
Global variables are dangerous and shouldn't be used except in special circumstances. There's no way to control access to them and any module may modify them.
The way I use variables like this is to have a static variable in one module and have an accessor function to allow other modules to read its value. The value gets read into a local (automatic) variable whenever it's needed. Another accessor function can be used to write a new value to the variable if allowed by the module.
If he's saying what I think he is, it's a variable that I'd call "of file extent", because "static" can be a confusing term. Because Brian Kernigan went and mixed his metaphors when he defined what "static" means: at the file level it means "of file extent", but within a function it means "keep the value around".
This is a very interesting observation, although I would not say it is a mixed metaphor. The C language, like most others, has been designed so that keywords have more than one semantic element attached to them.
In this case, "static" has (at least) two: 1) indicate static allocation, and 2) limit the scope.
When "static" is used at the file level rather than within a function, some aspects of element 2) change, although the high-level concept is still the same (the scope is limited in both cases, to the compilation unit or to the block, respectively) whereas element 1) stays the same.
As a side note, this is also the reason why a statement like "int a = 3;" has very different meanings, and is implemented in a very different way by the compiler, depending on whether it appears at the file level or within a function.
Yes, I mean static, file scope variable.
If there is an accessor function, then there is no need for any static, file scope variables. Whoever needs the value can call the accessor function and read it into local (automatic) variable?
Jorick: I believe now I understand. You are saying that accessor function will return the value of static, file scope variable? Anywhere in code base, value of static, file scope variable can be obtained by calling accessor function and assigning this value to local (automatic) variables?
Ideally, you should be avoiding the global variable altogether. Instead, try a client server mechanism. Server provides an API such as getVehicleSpeed() that can be exposed to all client modules. In this way, you can avoid redefining the same variable.
What is the cost of just calling this "low-level function" whenever you need the vehicle speed? It sounds like you've already got the function that shwami speaks of, but you're simply not using it -- why?
In my opinion, there are two aspects worth considering in this question.
The first one is about the basic mechanisms available at the language level to define variables with different scope, lifetime, and allocation. From this point of view the C language offers only a few choices.
Simplifying a little bit and barring non-standard language extensions, we have automatic variables (block scope and lifetime, on-stack allocation), two flavors of static variables (block or compilation-unit scope, unlimited lifetime, static allocation) and global variables (global scope, unlimited lifetime, static allocation).
When multithreading is supported, depending on the target system we may also have TSD, TLS and TM variables, but this is out of the scope of this discussion.
Globally-accessible functional wrappers around static variables are commonly used to implement more flexible/controlled access policies, besides what the language itself provides. This leads us to the second aspect of your question.
In a sound design, it is the design itself that determines which data structures should be shared among code modules, to which extent they are shared, and which access policy must be followed. Then, the available language features are leveraged to implement what is needed, not the opposite.
In this case, I would start by understanding how vehicle_speed must be shared according to design requirements. This will also shed more light on the exact meaning of "too visible" in your question. Afterwards, you can choose which kind of variable best supports those requirements and use wrappers to further refine the access policy.
One more wrinkle in an embedded system is that a variable "accessor" function may actually read a hardware value (such as an analog to digital converter).
In that case there can either be a cost to reading the value (because hardware is slow, or whatever), or there can be problems with having two different values for the "same" variable would be a Bad Thing (i.e., reading the velocity ADC twice in one control loop sampling interval).
I have absolutely no clue if that's the case here, of course, but I've certainly had to deal with these issues in embedded systems. Making sure that the right information shows up in the right spots at the right time is part of the overall challenge of making the system work correctly.
The recommendation to use "accessor functions" (a.k.a., "getters and setters" in OOP) is rather misguided, because it really does not "encapsulate" anything in terms of concurrency. Such "accessor functions" are still simply functions, so they always run in the caller's thread. If this caller is an ISR (interrupt service routine) or an RTOS thread, you have the exact same concurrency hazards as with "naked" global variables (only slower due to the function call overhead).
Also, many (most) respondents re-iterate how global variables should be avoided, but nobody really suggested a good, general alternative to avoid them.
So, let me here suggest such a strategy. It is called event-driven programming. Here, every change in the system is communicated by event objects, instead of global variables. Event objects are produced in a inherently consistent manner and only after all the information is prepared inside an event, the event is posted to an event queue. Arrival of an event in the queue provides then both the stimulus (e.g., unblocking of a processing thread) and the self-consistent information about this occurrence.
For example, in your case, you might have an event that provides stimulus VELOCITY_CHANGED and provides the new value of the velocity in the event parameter. The event might also contain a time stamp (when was this velocity read), maybe the acceleration at this time, etc. In other words, it can evolve with your system. The main point is that all data inside the event will be consistent. In contrast, if you used separate global variables (or "accessor functions"), this information might very easily be inconsistent.
Of course, I cannot explain here the whole event-driven programming paradigm. If you like to learn more, please visit: http://www.state-machine.com/doc/concepts.html
I would just like to point out that the debate around event-driven programming has been going on for more than 20 years.
It is an interesting approach for sure. However, until now, no clear agreement among researchers has been reached about its advantages with respect to lock-based or lock-free shared memory access techniques, especially in terms of inherent concurrency and consistency.
There is plenty of literature on the subject. Let's not oversimplify by stating that event-driven programming is the solution.
@Ivan Cibrario Bertolotti: I absolutely agree that the debate between proponents of shared-state concurrency and event-driven, Erlang-style concurrency has been going on for a long time, and the shared-state concurrency camp has apparently prevailed. So much so, that event-driven model fell into such obscurity that embedded developers typically aren't even aware of its existence.
And that's why the OP is asking the question about how to avoid global variables. This is because global variables are an inherent problem of shared-state concurrency, regardless whether or not you hide them behind "accessor functions", as I tried to explain in my previous post.
And yet, it seems that many experts on high-reliability systems independently re-discover the best practices of concurrent programming, which invariably favor asynchronous events and threads that are structured as event-loops and don't share data. As a good example of this trend, I would recommend the article by NASA JPL veteran, Dr. David Cummings "Managing Concurrency in Complex Embedded Systems" (http://www.kellytechnologygroup.com/main/concurren... ) Please note that Cummings does not even call his approach "event-driven programming", but this is what it really is.