My publisher has authorized me to release a free sample chapter from my book. They let me pick one, and I decided to go with the one on global variables. If this is a success, a couple more chapters may be released one way or another, so I'd welcome input on which the other best topics would be from the table of contents.
Entire chapter in Adobe Acrobat (166 KB):
http://koopman.us/bess/chap19_globals.pdf
Chapter 19
Global Variables Are Evil
• Global variables are memory locations that are directly visible to an entire
software system.
• The problem with using globals is that different parts of the software are
coupled in ways that increase complexity and can lead to subtle bugs.
• Avoid globals whenever possible, and at most use only a handful of globals
in any system.
Contents:
19.1 Chapter overview
19.2 Why are global variables evil?
Globals have system-wide visibility, and aren’t quite the same as static variables.
Globals increase coupling between modules, increase risk of bugs due to
interference between modules, and increase risk of data concurrency problems.
Pointers to globals are even more evil.
19.3 Ways to avoid or reduce the risk of globals
If optimization forces you into using globals, you’re better off getting a faster
processor. Use the heap or static locals instead of globals if possible. Hide
globals inside objects if possible, and document any globals that can’t be
eliminated or hidden.
19.4 Pitfalls
19.5 For more information
19.1. Overview
“Global Variables Are Evil” is a pretty strong statement! Especially for some-
thing that is so prevalent in older embedded system software. But, if you can
build your system without using global variables, you’ll be a lot better off.
Global variables (called globals for short) can be accessed from any part of a
software system, and have a globally visible scope. In its plainest form, a global
variable is accessed directly by name rather than being passed as a parameter. By
way of contrast, non-global variables can only be seen from a particular module
or set of related methods. We’ll show how to recognize global variables in detail
in the following sections.
19.1.1. Importance of avoiding globals
The typical motivation for using global variables is efficiency. But sometimes
globals are used simply because a programmer learned his trade with a
non-scoped language (for example, classical BASIC doesn’t support variable
scoping – all variables are globals). Programmers moving to scoped languages
such as C need to learn new approaches to take full advantage of the scoping and
parameter passing mechanisms available.
The main problem with using global variables is that they create implicit cou-
plings among various pieces of the program (various routines might set or mod-
ify a variable, while several more routines might read it). Those couplings are not
well represented in the software design, and are not explicitly represented in the
implementation language. This type of opaque data coupling among modules re-
sults in difficult to find and hard to understand bugs.
19.1.2. Possible symptoms
You may have problems with global variable use if you observe any of the fol-
lowing when reviewing your implementation:
- More than a handful of variables are defined as globally visible (ideally, there
are none). In C or C++, they are defined outside the scope of any procedure,
so they are visible to all procedures. An indicator is the use of the keyword
extern to access global variables defined outside the scope of your compiled
module.
- Variables are used in a routine that are neither defined locally, nor passed as
parameters. (This is just another way of saying they are global.)
- In assembly language, variables are accessed by label from subroutines or
modules rather than via being passed on the stack as a parameter value. Any
access to a labeled memory location (other than in a single module that has ex-
clusive access to that location) is using a global.
The use of global variables is sometimes defensible. But their usage should be
for a very few, special values, and not a matter of routine. Consider their occa-
sional use a necessary evil.
19.1.3. Risks of using globals
The problem with global variables is that they make programs unnecessarily
complex. That can lead to:
- Bugs caused by hidden coupling between modules. For example, misbehav-
ing code in one place breaks things in another place.
- Bugs created in one module due to a change in a seemingly unrelated second
module. For example, a change to a program that writes a global variable
might break the behavior of code that reads that variable.
It may be necessary to have variables that are global, or at least serve a similar
purpose as global variables. The risk comes from using global variables too
freely, and not using mitigation strategies that are available.
19.2. Why are global variables evil?
Global variables should be avoided to the maximum extent possible. At a high
level, you can think of global variables as analogous to GOTO statements in pro-
gramming languages (this idea dates back at least as far as Wulf (1973)). Most of
us reflexively avoid GOTO statements. It’s odd that we still think globals are
OK.
First, let’s discuss what globals are, then talk about why they should be
avoided.
... read more ...
(c) Copyright 2010 Philip Koopman
Companion blog to the book Better Embedded System Software by Phil Koopman at Carnegie Mellon University
Tuesday, December 25, 2012
Sunday, December 16, 2012
Software Timing Loops
Once in a while I see someone using code that uses a timing loop to wait for some time to go by. Code might look like this:
// You should *NOT* use this code as-is!
#define SCALE 15501 // System-dependent time scaling factor
void WaitMS(unsigned long ms)
{ unsigned long counter = ms * SCALE; // Compute how long to wait
while(counter > 0) { counter--;} // Waste time
}
The idea is that the designer tweaks "SCALE" so that WaitMS takes exactly one msec for each integer value of the input ms. In other words WaitMS(5) waits for 5 msec. But, there are some significant problems with this code.
Sometimes there isn't a hardware timer or there is some other compelling reason to use a software loop. If that's the case, the following advice might prove helpful.
(There are no doubt minor problems that readers will point out as well depending upon style preferences. As with all my code examples, I'm presenting a simple "how I usually see it" example to explain the concept. If you can find style problems then probably you already know the point I'm making. Some examples: WaitMS might check for overflow, uint32_t is better than "unsigned long," "const unsigned long SCALE = 15501L" is better if your compiler supports it, and there may be mixed-length math issues with the multiplication of ms * SCALE if they aren't both 32-bit unsigned integers. But the above code is what I usually see in code reviews, and these style details tend to distract from the main point of the article.)
// You should *NOT* use this code as-is!
#define SCALE 15501 // System-dependent time scaling factor
void WaitMS(unsigned long ms)
{ unsigned long counter = ms * SCALE; // Compute how long to wait
while(counter > 0) { counter--;} // Waste time
}
The idea is that the designer tweaks "SCALE" so that WaitMS takes exactly one msec for each integer value of the input ms. In other words WaitMS(5) waits for 5 msec. But, there are some significant problems with this code.
- Different compiler versions and different optimization levels could dramatically change the timing of this loop depending upon the code that is generated. If you aren't careful, your system could stop working for this reason when you do a recompile, without you even knowing the timing has changed or why that has happened.
- Changes to hardware can change the timing even if the code is recompiled. Changing the system clock speed is a fairly obvious problem. But other more subtle problems include clock throttling on high-end processors due to thermal management, putting the code in memory with a wait states for access, moving to a processor with instruction cache, or using a different CPU variant that has different instruction timings.
- The timing will change based on interrupt service routines delaying execution of the while loop. You are unlikely to see the worst case timing disruption in testing unless you have a very deterministic system and/or really great testing.
- This approach ties up the CPU, using power and preventing other tasks from running.
Sometimes there isn't a hardware timer or there is some other compelling reason to use a software loop. If that's the case, the following advice might prove helpful.
- Make sure you put the software timing loop in one place like this instead of having lots of small in-line timing loops all over the place. It is hard enough to get one timing loop right!
- The variable "counter" should be defined as "volatile" to make sure it is actually decremented each time through the loop. Otherwise the optimizer might decide to just eliminate the entire while loop.
- You should calibrate the "SCALE" value somehow to make sure it is accurate. In some systems it makes sense to calibrate a variable during system startup, or do an internal sanity check during outgoing system test to make sure that it isn't too far from the right value. This is tricky to do well, but if you skip it you have no safety net in case the timing does change.
(There are no doubt minor problems that readers will point out as well depending upon style preferences. As with all my code examples, I'm presenting a simple "how I usually see it" example to explain the concept. If you can find style problems then probably you already know the point I'm making. Some examples: WaitMS might check for overflow, uint32_t is better than "unsigned long," "const unsigned long SCALE = 15501L" is better if your compiler supports it, and there may be mixed-length math issues with the multiplication of ms * SCALE if they aren't both 32-bit unsigned integers. But the above code is what I usually see in code reviews, and these style details tend to distract from the main point of the article.)
Monday, November 26, 2012
Top 10 Best Practices for Peer Reviews
Here is a nice list of best practices for peer reviews from SmartBear. These parallel the recommendations I usually give, but it is nice to have the longer version readily available too (see link below).
1. Review fewer than 200-400 lines of code at a time.
2. Aim for an inspection rate of less than 300-500 LOC/hr (But, see comment below)
3. Take enough time for a proper, slow review, but not more than 60-90 minutes
4. Authors should annotate source code before the review begins
5. Establish quantifiable goals for code review and capture metrics so you can improve your process
6. Checklists substantially improve results for both authors and reviewers
7. Verify that defects are actually fixed
8. Managers must foster a good code review culture in which finding defects is viewed positively
9. Beware the "Big Brother" effect (don't use metrics to punish people)
10. The Ego Effect: do at least some code review, even if you don't have time to review it all
And now my comments: The data I've seen shows 300-500 LOC/hr is too high by a factor of 2 or so. I recommend 100-200 lines of code per hour for 60-120 minutes. It may be that SmartBear's tool lets you go faster, but I believe that comes at a cost that exceeds the time save.
I deleted their best practice #11, which says that lightweight reviews are great stuff, because I don't entirely buy it. Everything I've seen shows that lightweight reviews (which they advocate) are better than no reviews, and for that reason perhaps they make a good first step. But if you skip the in-person review meeting you're losing out on a lot of potential benefit. Your mileage may vary.
You can get the full white paper here: http://support.smartbear.com/resources/cc/11_Best_Practices_for_Peer_Code_Review.pdf
(They are not compensating me for posting this, and I presume they don't mind the free publicity.)
1. Review fewer than 200-400 lines of code at a time.
2. Aim for an inspection rate of less than 300-500 LOC/hr (But, see comment below)
3. Take enough time for a proper, slow review, but not more than 60-90 minutes
4. Authors should annotate source code before the review begins
5. Establish quantifiable goals for code review and capture metrics so you can improve your process
6. Checklists substantially improve results for both authors and reviewers
7. Verify that defects are actually fixed
8. Managers must foster a good code review culture in which finding defects is viewed positively
9. Beware the "Big Brother" effect (don't use metrics to punish people)
10. The Ego Effect: do at least some code review, even if you don't have time to review it all
And now my comments: The data I've seen shows 300-500 LOC/hr is too high by a factor of 2 or so. I recommend 100-200 lines of code per hour for 60-120 minutes. It may be that SmartBear's tool lets you go faster, but I believe that comes at a cost that exceeds the time save.
I deleted their best practice #11, which says that lightweight reviews are great stuff, because I don't entirely buy it. Everything I've seen shows that lightweight reviews (which they advocate) are better than no reviews, and for that reason perhaps they make a good first step. But if you skip the in-person review meeting you're losing out on a lot of potential benefit. Your mileage may vary.
You can get the full white paper here: http://support.smartbear.com/resources/cc/11_Best_Practices_for_Peer_Code_Review.pdf
(They are not compensating me for posting this, and I presume they don't mind the free publicity.)
Monday, June 4, 2012
Cool Reliability Calculation Tools
I ran across a cool set of tools for computing reliability properties, including reliability improvements due to redundancy, MTBF based on testing data, availability, spares provisioning, and all sorts of things. The interfaces are simple but useful, and the tools are a lot easier than looking up the math and doing the calculations from scratch. If you need to do anything with reliability it's worth a look:
http://reliabilityanalyticstoolkit.appspot.com
The one I like the most right now is the one that tells you how long to test to determine MTBF based on test data, even if you don't see any failures in testing:
http://reliabilityanalyticstoolkit.appspot.com/mtbf_test_calculator
Here is a nice rule of thumb based on the results of that tool. If you want to use testing to ensure that MTBF is at least some value X, you need to test about 3 times longer than X without ANY failures being observed. That's a lot of testing! If you do observe a failure, you have to test even longer to determine if it was an unlucky break or whether MTBF is smaller than it needs to be. (This rule of thumb assumes 95% confidence level and no testing failures observed -- as well as random independent failures. Play with the tool to evaluate other scenarios.)
http://reliabilityanalyticstoolkit.appspot.com
The one I like the most right now is the one that tells you how long to test to determine MTBF based on test data, even if you don't see any failures in testing:
http://reliabilityanalyticstoolkit.appspot.com/mtbf_test_calculator
Here is a nice rule of thumb based on the results of that tool. If you want to use testing to ensure that MTBF is at least some value X, you need to test about 3 times longer than X without ANY failures being observed. That's a lot of testing! If you do observe a failure, you have to test even longer to determine if it was an unlucky break or whether MTBF is smaller than it needs to be. (This rule of thumb assumes 95% confidence level and no testing failures observed -- as well as random independent failures. Play with the tool to evaluate other scenarios.)
Tuesday, May 15, 2012
CRC and Checksum Tutorial
I published a tutorial on CRCs and Checksums on the blog I maintain on that topic. Since I get a lot of hits on those search terms here, I'm providing a pointer for those who are interested:
http://checksumcrc.blogspot.com/2012/05/crc-and-checksum-tutorial-slides.html
http://checksumcrc.blogspot.com/2012/05/crc-and-checksum-tutorial-slides.html
Friday, May 4, 2012
The End of ESP/ESD Magazine
The print edition Embedded System Design (formerly Embedded System Programming) has published its last issue. For folks who have been doing this for a long time this is a huge change in the embedded systems landscape. Jack Ganssle has an article about his experiences in his last regular column (http://www.eetimes.com/discussion/break-points/4372144/Farewell--ESD). They're going to invest effort into the web version, and I wish them all the best. Hopefully it will be even better, but it won't be the same.
I started doing embedded systems while I was an undergrad at RPI. My first big project was writing microcode for a graphics display system at IBM over a summer, complete with fixed point trig functions. Then I wrote the software for a control system that ran a hotel in the early 1980s. When I say "ran" I mean turning power off to unused rooms, running a timer for a 3-hour TV movie rental system, giving room status to the maid break room monitor, and running a fairly complex cash register. The entire thing ran on an Atari 1200 at first (IBM PC original later, when it came on the market). Those systems ran at a handful of MHz and had 64KB of memory. Only floppy disks -- hard disks were too expensive. It was all programmed in Forth (no other language of that era could pull that off on an inexpensive personal computer, and the company learned that the hard way by trying others). Since then computers in hotels have become common. But we were there in the first wave on automation side (others did bookkeeping at about the same time), and it was a fun ride.
I then got my undergrad degree, drove US Navy submarines, did some combat system integration, got a PhD, and then parlayed a CPU design startup venture from my bedroom into a job with Harris Semiconductor doing embedded controller design. I managed to get two articles into Byte magazine along the way (remember that? or at least heard of it?). One of them was a cover article on a particular style of computer design (April 1987). When I visited the Smithsonian computer history exhibit there was a huge stack of Byte magazines on display, and I could see the spine of that issue buried in the stack. Nothing makes you feel older than seeing something you wrote in a museum!
Jack has posted a copy of the very first ESP magazine from 1988 posted here: http://www.ganssle.com/misc/firstesp.pdf In addition to several pictures of original IBM PC "clone" systems (as we called them in the 80s) you'll also see a 4-page spread for the RTX CPU series on pages 53-56. Those are the guys I started working for in 1989, designing the 32-bit version of the processor family and helping with evolution of the 16-bit version they already had been working on. I found and started reading ESP around the second or third issue, and have read every single issue since. I also assign selected articles from back issues to students in my courses today as a way to supplement embedded system textbooks with articles that in many cases are more directly applicable to the real world. I contributed a few articles to ESP over the years and did the Embedded Systems Conference a few times -- most recently about a year ago in Silicon Valley. It's always fun to connect with the community that way.
I've been doing embedded systems one way or another since I got my degrees, including a few years at United Technologies (mostly elevators and automotive, but also time with jet engines, radars, sonars, helicopters, HVAC systems, and things I'm forgetting). Then I went to Carnegie Mellon to work on wearable computers, software robustness testing, embedded networking, graceful degradation, embedded system safety, and embedded system security. And most recently, autonomous vehicle dependability.
Along the way I've kept up with ESP/ESD even though I was really supposed to be spending more time reading journals and other academic-type stuff. But the embedded system articles are more fun! I've noticed the magazine getting thinner and thinner in recent years, and figured it would eventually come to an end. But it is still sad to see it go. Embedded systems as a profession is quite decentralized. For many years the only real common element that professionals were likely to have was a subscription to that magazine. Other publications have appeared, blogs like this one have popped up, and I know the ESD web site will live on. Hopefully in ten years the community will have even better resources available. We'll see how it turns out.
(PS: for those of you who have noticed no blog postings in a while, this blog and I are both still alive. I have just been that busy. I expect to get back to regular posting eventually. And my CRC blog will see more activity when I get some research funding permissions issues cleared up, although that may be a while.)
Monday, February 27, 2012
Floating Point Comparison Problems
We all learned (or should have learned) that comparing floating point numbers for equality is a risky proposition. The code:
Today I want to talk about a different comparison problem that is more subtle, and frankly one I never even thought about until I saw it as a failure in a real system that had (everyone thought) been thoroughly tested.
Consider code that looks like this:
#define SAFETYLIMIT 357.9
double MonitoredValue;
. . .
... compute MonitoredValue based on sensor values ...
if (MonitoredValue > SAFETYLIMIT)
{ ... slow down or shut down system ... }
The idea is to measure an actual value and check against a safety limit. If the value is too high, command a lower actuation set-point or do a safety shutdown. Alternately, the code might check to make sure that the monitored value is close enough to a commanded value. The details don't matter beyond there being a floating point value comparison of any type involved.
How, you ask, can this fail since it is not an exact equality test? The answer is that the computation of MonitoredValue could result in a numeric exception and produce a NaN value ("Not a Number"). Division by zero is a classic way to get this problem, but there are other more subtle problems involving numeric underflow, overflow, or hitting a discontinuity in a trig function. Any comparison involving a NaN fails, depending upon your compilation flags, floating point library, and so on. So, you could have a really fast speed that results in a numerical exception and the speed limit won't work. Because this is a numerical exception problem, using a double instead of a float usually won't help.
There are at least three solutions to this problem. One is to always make sure that NaN values result in a "safe" outcome. I'd recommend against this -- it is just too easy to forget, get wrong, or get into a situation where you aren't even sure what the safe action is. (Consider that most code doesn't bother to check for null pointers. In most projects, checking for NaN just isn't going to happen.)
Another solution is to use integers or fixed point math instead of floating point math. Fixed point computations are a pain, but at least they don't have a NaN value.
The last one I can think of is to set up your compiler or run-time system to trap on NaN comparisons. (For example,
None of theses solutions is perfect -- but having a plan to deal with this situation is a good first step.
(BTW, there is a LOT more to writing safety critical code than putting in a speed limit check in the main code, so please consider this just an example to motivate the NaN problem. If you have a safety-critical system that doesn't have a mechanical safety backup, you need to do a whole lot more than this before you can trust software to get it right.)
if (a == b) { ... }
could easily evaluate as not equal rather than equal if a and b differ due to round-off error. There are ways to deal with that involving making sure that the difference is a small fraction of the value. But as I said, you've probably seen this one.Today I want to talk about a different comparison problem that is more subtle, and frankly one I never even thought about until I saw it as a failure in a real system that had (everyone thought) been thoroughly tested.
Consider code that looks like this:
#define SAFETYLIMIT 357.9
double MonitoredValue;
. . .
... compute MonitoredValue based on sensor values ...
if (MonitoredValue > SAFETYLIMIT)
{ ... slow down or shut down system ... }
The idea is to measure an actual value and check against a safety limit. If the value is too high, command a lower actuation set-point or do a safety shutdown. Alternately, the code might check to make sure that the monitored value is close enough to a commanded value. The details don't matter beyond there being a floating point value comparison of any type involved.
How, you ask, can this fail since it is not an exact equality test? The answer is that the computation of MonitoredValue could result in a numeric exception and produce a NaN value ("Not a Number"). Division by zero is a classic way to get this problem, but there are other more subtle problems involving numeric underflow, overflow, or hitting a discontinuity in a trig function. Any comparison involving a NaN fails, depending upon your compilation flags, floating point library, and so on. So, you could have a really fast speed that results in a numerical exception and the speed limit won't work. Because this is a numerical exception problem, using a double instead of a float usually won't help.
There are at least three solutions to this problem. One is to always make sure that NaN values result in a "safe" outcome. I'd recommend against this -- it is just too easy to forget, get wrong, or get into a situation where you aren't even sure what the safe action is. (Consider that most code doesn't bother to check for null pointers. In most projects, checking for NaN just isn't going to happen.)
Another solution is to use integers or fixed point math instead of floating point math. Fixed point computations are a pain, but at least they don't have a NaN value.
The last one I can think of is to set up your compiler or run-time system to trap on NaN comparisons. (For example,
-fsignaling-nans and other related options for GCC if they are supported.) What you do when you trap is not necessarily simple, but at least your system will know there is a problem instead of blinding going along ignoring safety limits.None of theses solutions is perfect -- but having a plan to deal with this situation is a good first step.
(BTW, there is a LOT more to writing safety critical code than putting in a speed limit check in the main code, so please consider this just an example to motivate the NaN problem. If you have a safety-critical system that doesn't have a mechanical safety backup, you need to do a whole lot more than this before you can trust software to get it right.)
Subscribe to:
Posts (Atom)
Static Analysis Ranked Defect List
Crazy idea of the day: Static Analysis Ranked Defect List. Here is a software analysis tool feature request/product idea: So many times we...
-
(If you want to know more, see my Webinar on CRCs and checksums based on work sponsored by the FAA.) If you are looking for a lightwei...
-
I recently worked with some embedded system teams who were struggling with the best way to use .c and .h files for their source code. As I w...
-
If you want to estimate how long it's going to take to create a piece of embedded software (and how much it will cost), it's useful ...