You are currently browsing the Sticky Bits blog archives for September, 2010.

Scope and Lifetime of Variables in C

September 27th, 2010

In a previous posting we looked at the principles (and peculiarities) of declarations and definitions. Here I would like to address the concepts of scope and lifetime of variables (program objects to be precise).

In the general case:

  • The placement of the declaration affects scope
  • The placement of the definition affects lifetime

Lifetime

The lifetime of an object is the time in which memory is reserved while the program is executing. There are three object lifetimes:

  • static
  • automatic
  • dynamic

Given the following piece of code:

int global_a;       /* tentative defn; become actual defn init to 0 */
int global_b = 20;     /* defn and implicit-decl */

int f(int* param_c)
{
   int local_d = 10;
   . . .
   return local_d;
}
int main(void)
{
   int *ptr = malloc(sizeof(int)*100);
   ...
   global_a = f(ptr);
   ...
   free(ptr);
}

global_a and global_b are static
The memory allocated by the call to malloc is dynamic
All others (including param_c, ptr and the return value from function f) are automatic.

Static Objects

The memory for static objects is allocated at compile/link time. Their address is fixed by the linker based on the linker control file (LCF).  You may know this file by another name such as linker-script file, linker configuration file or even scatter-loading description file. The LCF file defines the physical memory layout (Flash/SRAM) and placement of the different program regions.

The static region is actually subdivided into two further sections, one for initialised-definitions (int global_ b = 20;)  and one for uninitialized-definitions (int global_a;). So it would not be unexpected for the address of global_a and global_b to not be adjacent to each other in SRAM. The uninitialised-definitions’ section is commonly known as the .bss or ZI section. The initialised-definitions’ section is commonly known as the .data or RW section.
Finally, the initial value of global_a will be zero (0) and 20 for global_b.

Automatic objects

The majority of variables are defined within functions and classed as automatic variables. This also includes parameters and any temporary-returned-object (TRO) from a non-void function, e.g.

int f(int* param_c)  /* tro(int) and parameter(param_c) */
{  
   int local_d = 10; /* local variable */
   . . .
   return local_d;   /* copy local_d to tro */
}

The default model in general programming is that the memory for these program objects is allocated from the stack. For parameters and TRO’s the memory is normally allocated by the calling function (by pushing values onto the stack), whereas for local objects, memory is allocated once the function is called. This key feature enables a function to call itself – recursion (though recursion is generally a bad idea in embedded programming as it may cause stack-overflow problems).
In this model, automatic memory is reclaimed by popping the stack on function exit.

Within a function variables may be localised to a block associated with a control structure, e.g.

for(x = 0; x < N; ++x) {
   int block_y = 0;   /* nested local variable */
   . . .
}

Here the memory is allocated on entry to the block and reclaimed on exit.
However, on most modern microcontrollers, especially 32-bit RISC architectures, automatics are stored in scratch registers, where possible, rather than the stack. For example the ARM Architecture Procedure Call Standard (AAPCS) defines which CPU registers are used for function call arguments into, and results from, a function and local variables.

Importantly, if an automatic is not explicitly initialised, then the initial value is indeterminate (thus garbage) and therefore should never be read before being set. If the automatic is explicitly initialised then the memory is reinitialised on each call of the function.
The location and size of the stack are typically defined using the LCF.
Finally, there still are the (historic) keywords auto and register that can be applied to automatics. Both are pretty much redundant in modern programming.

Dynamic Objects

Strictly speaking (according to the C standard) dynamically allocated objects are also called automatics. However, it is important to differentiate between this type of object and automatics for two reasons:

  1. The memory is allocated from a different memory area (the heap not the stack)
  2. The lifetime is under the control of the programmer rather than the C run-time system.

When calling on malloc, calloc or realloc, these functions return an address (void*) for a block of dynamically allocated memory. The lifetime of this memory is from allocation until the call to either free or realloc the memory.

The realloc function takes an allocated memory block and expands (or contracts) it to a bigger (or smaller) size. This may involve moving the chunk of memory and copying over the old contents. When this is done, the old contents are automatically freed.

The contents of the memory return from malloc are indeterminate; whereas for calloc the memory is initialised to all zeros. If realloc expands the allocated memory area, then the contents of the extra expended area are indeterminate.
The size and location of the heap are also usually defined in the LCF.

Programming errors involving not releasing dynamically allocated memory have been, and still are, a major source of run-time errors (memory leaks). This is why most modern language use garbage collection (which limits their applicability to many real-time embedded applications) and why many coding standards, such as MISRA-C, ban dynamic memory allocation.

Static local variables

Before we leave lifetimes, there is one further anomaly. The keyword static can be applied to a local variable, e.g.

#include <stdio.h>
void f1(void)
{
   static int slocal = 10;        /* static local */
   int alocal = 10;              /* automatic local */
   printf("In f1: slocal = %d, alocal = %d\n", slocal, alocal);
   ++slocal;
}

int main(void)
{
   f1();
   f1();
   f1();
}

Applying static to a local variable changes the objects lifetime from automatic to static. This means that the memory is allocated at compiler/link time and its address in memory is fixed. However, as the memory is static these local variables retain their value from function call to function call. The local static is initialised only the first call of the function. So given the example above, the output is:
In f1: slocal = 10, alocal = 10
In f1: slocal = 11, alocal = 10
In f1: slocal = 12, alocal = 10

Local statics may look useful, however they cause major problems when trying to port code to a multi-task/multi-threading environment, and should generally be avoided where possible.

Scope

The scope of an object is the part of the program where the variable can be accessed (i.e. it is visible). The scope of an object generally falls into one of two general categories:

  • File scope
  • Block scope

As explained in the posting on declarations and definitions, a variable must be declared before it is accessed. Hence the scope of a variable is determined by the placement of its declaration. Returning to the previous example (slightly modified):

int global_a;       /* Decln and Defn */

int f(int* param_c)
{
   int local_d = param_c;       /* automatic local */
   static int local_s = 10;     /* static local    */
   . . .
   local_s = global_a;
   . . .
   return local_d;
}

int main(void)
{
   int *ptr = malloc(sizeof(int)*100);
   ...
   global_a = f(ptr);
   ...
   free(ptr);
}

In the example given, identifier global_a has file scope, whereas all other variables have block scope.

File Scope

Any variable declared with file scope can be accessed by any function defined after the declaration (in our example both f and main can access global_a). If global_a was declared after the function f but before main it would only be accessible within main.

Block Scope

Block scope is defined by the pairing of the curly braces { and } .  A variable declared within a block can only be accessed within that block. For example, local_d has block scope determined by the function-block for f and cannot be accessed outside that function. The variable ptr also has function-block scope limited to the main function. Note also that the local static, local_s, has block scope even though it has static lifetime.
Interestingly the parameter of function f, param_c, is also classed as have block scope. It can be accessed anywhere within the function it is a parameter of. Personally I would prefer to define this as “function” scope, but that would be incorrect according to the standard!

Within a function further localised (inner) scopes can be introduced, e.g.

for(x = 0; x < N; ++x) {
   int block_y = 0;
   . . .
}

Here, block_y is scoped to within the for-loop (i.e. it cannot be accessed in the for-expression region or outside of the for-block).

In a file and/or function we can have overlapping scopes, e.g.

int k = 20;
int main()
{
   int k = 10;
   printf( "In main, k is %d\n", k);
}

The rule is that an inner scope identifier always hides an outer scope identifier. Hence, the block-scoped identifier k hides the file-scoped identifier k (and thus the value displayed will be ten). Note that the file-scoped k is still in scope but is rendered invisible. It is generally bad practice to have variables with overlapping scopes.

Good programming practices limit scope as much as possible. By localising scope the potential for programming errors to creep in are significantly reduced.

Scope of Dynamic Objects

So it can be seen that the general case is that static objects have file scope and automatic objects have function scope. But what about the scope of dynamic objects?
A dynamic object doesn’t actually have scope, as such. In effect, its scope is dictated by the scope of any pointer holding the address of the dynamically allocated memory. As long as the pointer is in scope it can be dereferenced and the memory accessed.

External and Internal Linkage

Before leaving scope there is one final item to address. By default a variable with file scope can be accessed by any function in the whole program (e.g. in other files from where it is defined) as long as it is declared in scope for the function.
If a variable is defined with file scope in one file, but is required in another, then it can be brought into scope using the “extern” storage-class specifier, e.g.

/* file a.c */
int global_a = 10;       /* definition of global_a */

int f(int* param_c)
{
   int local_d = param_c;
   static int local_s = 10;
   . . .
   local_s = global_a;
   . . .
   return local_d;
}

/* file main.c */
extern int global_a;    /* declaration of global_a, now visible */
int f(int*);

int main(void)
{
   int *ptr = malloc(sizeof(int)*100);
   ...
   global_a = f(ptr);  /* global_a is visible so can be accessed */
   ...
   free(ptr);
}

Quite often we have the case where we need a variable with static lifetime, we don’t want it globally accessible (i.e. want to limit its use to functions in the current file), but we don’t want to define it as a local static as it is needed in multiple local functions.
To achieve this we can use the keyword static, but this time to affect scope rather than lifetime. If a file scoped variable is tagged as static then it has, what is called, internal linkage, e.g.

/* file a.c */
int global_a = 10;      /* external linkage – global scope */
static int internal_b;    /* internal linkage – this-file scope  */

int f(int* param_c)
{
   int local_d = param_c;   /* function scope, auto */
   static int local_s = 10; /* function scope, static */
   . . .
   local_s = global_a;
   . . .
   return local_d;
}

If another file tried to declare internal_b as extern, then this would result in a link-time error.
Note that internal linkage can also be applied to functions. All functions have external linkage by default, so it is very good practice to declare a function as static if it is only being used with the current file.

Next time: Why understanding Scope and Lifetime is important to embedded programming

The Baker’s Dozen of Use Cases

September 6th, 2010

RULE 2: Understand your stakeholders

A Stakeholder is a person, or group of people, with a vested interest in your system. Vested means they want something out of it – a return on their investment. That may be money; it may be an easier life.


One of the keys to requirements analysis is understanding your stakeholders – who they are, what they are responsible for, why they want to use your system and how it will benefit them.

It’s important to understand (and difficult for many software engineers to accept!) your stakeholders have responsibilities above and beyond just using your product. In fact, the only reason they are using your product is because it (should!) help them fulfil their larger responsibilities. If your product doesn’t help your stakeholder then why should they use it?


The first step in requirements analysis is to define your stakeholders. That definition must include:

  1. A named individual responsible for the stakeholder group
  2. The stakeholder’s responsibilities. That is, a description of the roles, jobs, and tasks the stakeholders have to perform everyday. If you understand a stakeholder’s problems and needs you can define solutions that help them
  3. Success criteria. That is: what is a good result for this stakeholder? The success criteria are a list of features and qualities that, if implemented, would bring maximum benefit to the stakeholder group.



Not all stakeholders are the same. For analysis, I divide stakeholders into three groups using my ‘stakeholder onion’ model:


Users.
Users are the most obvious group of stakeholders. Users directly interact with the system under development (sometimes I call them ‘Direct Interactors’).  A system’s users are concerned with how things work – what buttons to press, what order events happen, etc.  Their focus is therefore primarily functional behaviour and human-centred system qualities-of-service such as usability.

Beneficiaries.
The beneficiaries have some need that the system fulfils (or some pain that needs to be taken away!). The beneficiaries therefore benefit (often financially) by having the system in place. Typically, these stakeholders will be paying for the system. Beneficiaries are less interested in function and more interested in quality-of-service – reliability, maintainability, etc. since if these requirements are not fulfilled it will cost them money!

Constrainers.

The Users and Beneficiaries of a system are concerned with the problem they need to solve.   The Constrainers are focused not on the problem domain but on the solution domain.  The constrainers place negative requirements – or design constraints – on the system.  They place limits on how the system can work, how it will be developed, or what technologies or methodologies may be used. Constrainers come in many forms – Legislation, Standards, The Laws of physics, to name a few.

Most engineers intuitively understand they are, in some way, a stakeholder in the system they are designing, but they often do not know how to express this.  The development team itself is a Constrainer stakeholder, since it places limits on the technologies that can be implemented (lack of skills) or timescales (lack of resource).




Not only do the different stakeholders have different viewpoints, they also have different priorities on your project:

Beneficiaries’ concerns typically (but not always) outweigh user concerns.  For example, in the conflict between usability (a user concern) and low cost (a beneficiary concern) who will win? Remember: He who pays the piper calls the tune…

Constrainers should over-ride beneficiaries. Legal requirements, standards requirements, skills shortages, etc. will always supersede the desires of the other stakeholders.

The core difference between Beneficiaries and Constrainers is that Constrainers CANNOT be influenced – that is, you can negotiate on functional behaviours or qualities of service, but you cannot negotiate away legal requirements or the laws of physics!  A Constrainer either exists, in which case their criteria must be met; or they are not a Constrainer.   The skill, therefore, is to reduce the number of Constrainers on a project to open up as many different design options as possible.




Where does all this fit into Use Case analysis?  The answer is two-fold:

  • Actors on a use case diagram are all stakeholders
  • Stakeholder analysis gives context to the Use Case.

An Actor is an “external entity that interacts with the system under development”.  In the simplest case, that means they’re a User stakeholder.  The Beneficiaries and Constrainers also influence and affect the system behaviour, albeit in a very different way to the Users.  

Actors, Stakeholder types and Use Cases is a potential source of problems; and we’ll discuss this in a later article.


The most important reason for performing stakeholder analysis is it gives context to the Use Case model.  The Use Case System Boundary element defines the functional scope of the system – that is, what behaviours the system is required to provide – but it doesn’t give any reasons why the system has to provides those features.   There is nothing on the Use Case diagram that gives any help in validating the Use Case model.   We can add any behaviour we like to the Use Case diagram, provided we can link it to an actor.

By providing a stakeholder analysis we can validate the Use Cases by asking questions such as: Does the Use Case behaviour help the stakeholder (actor) fulfil their responsibilities?  If the behaviour does not help any stakeholder fulfil their responsibilities, should we be implementing it, or have we missed a stakeholder?  Are there other (additional) behaviours the stakeholder may need in order to fulfil their responsibilities?




<<Prev     Next>>

%d bloggers like this: