Efficient C Tips #5 – Make ‘local’ functions ’static’

Saturday, December 13th, 2008 by Nigel Jones

In my humble opinion, one of the biggest mistakes the designers of the ‘C’ language made, was to make the scope of all functions global by default. In other words, whenever you write a function in ‘C’, by default any other function in the entire application may call it. To prevent this from happening, you can declare a function as static, thus limiting its scope to typically the module it resides in. Thus a typical declaration looks like this:

static void function_foo(int a)
{
}

Now I’d like to think that the benefits of doing this to code stability are so obvious that everyone would do it as a matter of course. Alas, my experience is that those of us that do this are in a minority. Thus in an effort to persuade more of you to do this, I’d like to give you another reason – it can lead to much more efficient code. To illustrate how this comes about, let’s consider a module called adc.c This module contains a number of public functions (i.e. functions designed to be called by the outside world), together with a number of functions that are intended to be called only by functions within adc.c. Our module might look something like this:

void adc_Process(void)
{
&nbsp ...
&nbsp fna();
&nbsp ...
&nbsp fnb(3);
}

...

void fna(void)
{
&nbsp ...
}

void fnb(uint8_t foo)
{
&nbsp ...
}

At compile time, the compiler will treat fna() and fnb() like any other function. Furthermore, the linker may link them ‘miles’ away from adc_Process(). However, if you declare fna() and fnb() as ’static’, then something magical happens. The code would now look like this:

static void fna(void);
static void fnb(uint8_t foo);

void adc_Process(void)
{
&nbsp ...
&nbsp fna();
&nbsp ...
&nbsp fnb(3);
}

...

static void fna(void)
{
&nbsp ...
}

static void fnb(uint8_t foo)
{
&nbsp ...
}

In this case, the compiler will know all the possible callers of fna() and fnb(). With this information to hand, the compiler / linker will potentially do all of the following:

  • Inline the functions, thus avoiding the overhead of a function call.
  • Locate the static functions close to the callers such that a ’short’ call or jump may be performed rather than a ‘long’ call or jump.
  • Look at registers used by the local functions and thus only stack the required scratch registers rather than stacking all of the registers required by the compiler’s calling convention

Together these can add up to a significant reduction in code size and a commensurate increase in execution speed.

Thus making all non public functions not only makes for better code quality, it also leads to more compact and faster code. A true win-win situation! Thus if you are not already doing this religiously, I suggest you go through your code and do it now. I guarantee you’ll be very pleased with the results.

Next Tip
Previous Tip

A Request …

If I’m to believe the statistics for this blog, it appears that I’m gradually building a decent sized readership. Furthermore many of you choose to come back and read the latest postings which tells me that I’m doing something of value. Anyway, if this describes you, I’d be obliged if you’d encourage your colleagues to read the blog and also to post comments / questions. Why do I ask this? Well, an increased readership has several benefits, for both me and you the readers.

  • I believe quite passionately about improving the quality of embedded systems. Those of us that are working in this field collectively have an enormous impact on the world. Thus anything that helps improve the quality of embedded systems in turn helps improve the world. (I appreciate that this is a little melodramatic. It is, however, true).
  • Writing about something is the best way to I know to find out if I truly understand it. Thus, the very act of publishing a blog causes me to improve my skills and knowledge.
  • Some of the (too few) comments I get are quite profound and often instructive. Thus I also learn in this way.
  • The bigger the readership I have, the more inclined I am to publish. If I’m publishing things of value, then presumably the readers benefit.

Anyway, if you concur, then please encourage your colleagues. If you don’t, then that’s OK as well.

Thanks for reading.

Home

13 Responses to “Efficient C Tips #5 – Make ‘local’ functions ’static’”

  1. victorh says:

    I believe that using the same keyword ’static’ for two different concepts (namely: storage class specifier and function scope) is the main reason for some many people ignoring the benefits of making functions local.Personaly, in my projects I use to#define LOCAL static, so make clear with LOCAL void foo (void); that I want foo be a local function for its module.Moreover, this helps when I need to find some static variable: when I fire the search window in my editor, it does not stop in “false positives”, i.e., static-defined functions.(Nigel, please delete my previous comment)

  2. Bruno Santiago says:

    Wonderful tip victorhI will use it. :D And NigelI´m already encouraging people to read your blog and your “C Test”.I´m about to graduate on Eletrical Engineering and I don´t have any formal study on programming. My knowledge is mostly self-taught and I learn a lot with your blog.

  3. Ignacio Piqueras says:

    Well, I am software engineer and I have some formal study on programming, but I assure you that embedded programming is still unknown in many Faculties.I work with MCUs only for two years (8051 and ARM) and I find it amazingly interesting. I try to learn about “good coding” and your articles -and this blog- are really useful. I have a lot to learn.So I wanted to thank your job! Keep posting!

  4. Uhmmmm says:

    @victorhI see both those uses of static as two cases of the same concept. For static local variables, the varaible is really a global variable – it exists for the entire life of the program, just like any other global variable. The only difference is that it’s only accessible (by name anyways) within the function it’s declared in.Similarly for static functions: they are only accessible (by name) within the module they’re defined.In the case of static functions, you can pass pointers to the functions to other parts of the program to user use it from other compilation units. I presume you can with static local variables as well, but I’ve never had the need to.

  5. Nigel Jones says:

    I’ve never tried to pass the address of a static function to a function outside the module. I suspect that you’d have to engage in some casting in order to do it. Notwithstanding this, doing so would IMHO be incredibly dangerous and foolhardy. However, I take your point that static in of itself does not provide rock solid protection.

  6. Uhmmmm says:

    I’ve seen it used to do object-oriented C code. You have some way to create an object with some standard, known interface. The object contains a vtable – just a structure containing pointers to functions with known signatures. The function that created the object fills in the vtable (or simply points it to a static copy somewhere, or similar). The actual functions pointed to are often made static, to avoid cluttering the namespace.

  7. todd says:

    @ Uhmmmm.Actually static local variables are quite useful. Because they maintain state between calls, they can be used to create state variables in state machines for example. Or maintain timer counts, etc.But they are local, so they use the smallest applicable scope, which is good practice, for both optimization as well as just good style.The only negative I've seen is with debugging. Some compilers put them in a different segment and then forget to make that segment available to the debugger. But that was a few years ago, so probably not an issue anymore.

  8. Rick Mann says:

    Note that this is dependent on the compiler. LLVM, for example, is capable of whole-program optimization, and can inline method calls across translation units.

  9. Nigel Jones says:

    Rick – are you advocating not doing this if your compiler happens to perform global program optimization?

  10. MarkK says:

    At my current job we pass static function pointers around extensively – to other modules as well. This is particularly useful for timer expiry routines, and other callback functions which should not have a public interface.As for the local call/long call, I haven't even thought of the potential problems with them. However it should not be an issue unless there is a different assembler return instructions for each.

  11. ashleigh says:

    Keeping things static (local to a module) is just plain good practice.It makes the job of the linker easier (there is basically a whole bunch of stuff it never even has to go near). It avoids namespace pollution (which is REALLY important in compilers that dont support namespaces in their own right). [NOTE here that an exported naming convention helps enourmously. More below.]It avoids name clashes (I once spent WEEKS tracking down a weird defect when some of my code was linked with some vendor code – we both used the same name – so the linker made them the same piece of storage. More important for module-local variables).Once you do some embedded Ada programming and realise that some important principles are forced on you, its easy then to use those in C as well:- Stuff used in a unit should be local only to that unit (and not visible by accident or design ANYWHERE ELSE). [This does not apply to function pointers.]- Stuff exported by a unit should always be explicitly exported, by declaration in a header file, and by explicit action in the body.- Header files are only for exports. If you have a variable, define, constant, enum, typdef used only in a units "C" file, then put in in the C file, never in the header.And… use a naming convention. The file "fred.h" should prefix EVERY exported thing from the fred unit with "fred_". (This is similar to the idea of namespaces, object references, and in Ada, eliminating the "with" statement so that you must refernce things from another unit using the dotted notation.)This approach makes it REALLY easy to see where an identifier came from, at a glance, without needing some clever GUI/IDE to track identifer names and their originating units.Another dirty trick I use is to define:#define exported extern#define exportThen in header files, I write:exported UINT8 fred_do_calc(stuff);Here – exported is like a declaration: "this is a thing that is being exported" – so its visble in the outside world.And in the C file that corresponds, the function would appear as:export UINT8 fred_do_calc(stuff){more stuff}And here – "export" is like a directive: "EXPORT THIS FUNCTION!"I find this greatly enhances the readability and understandability of the code.I also lump ALL module local stuff together with a big comment block "LOCAL FUNCTIONS", and all exported stuff together under a comment block "EXPORTED FUNCTIONS".Imposing this discipline into the source code tends to force a more logical way of thinking about whats exposed and whats not. Using a standard empty header and body file template with all these blocks in makes it a no brainer when writing a new code unit.

  12. Ten-Seng Guh says:

    @todd"But that was a few years ago, so probably not an issue anymore."Yep, still an issue with Code Composer 3.3 at least.

  13. Greg Nelson says:

    Ashleigh writes: "The file "fred.h" should prefix EVERY exported thing from the fred unit with "fred_"."That's an interesting approach. Our style conventions do the opposite. Everything function for variable that is, can be, or should be "static" is prefixed with a module-specific string (e.g. CPU_write_date()), while the exposed user interface is all done with mixed case and no prefixes (e.g. SetDate()).The mixed case helps by ensuring that the exposed functions aren't overlapping with library functions. Leaving off the prefixes makes the calling code (at least to those of us brought up this way) more readable, because it isn't so long-winded. The prefixes group the functions together in various debugging tools so that the relevant pieces are easier to find.In a way, though, the ADA/C debate always reminds me of the VHDL/Verilog debate. Our company chose Verilog after reading about a challenge where about 20% of the Verilog coders got a working program, while 100% of the VHDL coders hadn't finished writing when their time ran out!

Leave a Reply