0% found this document useful (0 votes)
43 views35 pages

IES - UNIT - 3 - Notes

It is helpful for the students who want to learn about information security

Uploaded by

pk5178793
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
43 views35 pages

IES - UNIT - 3 - Notes

It is helpful for the students who want to learn about information security

Uploaded by

pk5178793
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

INTRODUCTION TO EMBEDDED SYSTEMS

UNIT – III: Embedded Firmware Design and Development


Embedded Firmware
The control algorithm (Program instructions) and or the configuration settings that an embedded
system developer dumps into the code (Program) memory of the embedded system. It is an un-
avoidable part of an embedded system.

The embedded firmware can be developed in various methods like


 Write the program in high level languages like Embedded C/C++ using an Integrated
Development Environment (The IDE will contain an editor, compiler, linker, debugger,
simulator etc. IDEs are different for different family of processors/controllers).
 Write the program in Assembly Language using the Instructions Supported by your
application‟s target processor/controller.

Embedded Firmware Design


 The embedded firmware is responsible for controlling the various peripherals of the
embedded hardware and generating response in accordance with the functional
requirements of the product.
 The embedded firmware is the master brain of the embedded system.
 The embedded firmware imparts intelligence to an embedded system.
 It is a onetime process and it can happen at any stage.
 The product starts functioning properly once the intelligence imparted to the product by
embedding the firmware in the hardware.
 The product will continue serving the assigned task till hardware breakdown occurs or a
corruption in embedded firmware.
 In case of hardware breakdown, the damaged component may need to be replaced and for
firmware corruptions the firmware should be re-loaded, to bring back the embedded
product to the normal functioning.
 The embedded firmware is usually stored in a permanent memory (ROM) and it is non
alterable by end users.
 Designing embedded firmware requires understanding of the particular embedded product
hardware, like various component interfacing, memory map details, I/O port details,
configuration and register details of various hardware chips used and some programming
language (either low level Assembly Language or High level language like C/C++ or a
combination of the two)
 The embedded firmware development process starts with the conversion of the firmware
requirements into a program model using various modeling tools.
 The firmware design approaches for embedded product is purely dependent on the
complexity of the functions to be performed and speed of operation required.
 There exist two basic approaches for the design and implementation of embedded
firmware, namely;

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 1


o The Super loop based approach
o The Embedded Operating System based approach
 The decision on which approach needs to be adopted for firmware development is purely
dependent on the complexity and system requirements.

1. Embedded firmware Design Approaches – The Super loop:


o The Super loop based firmware development approach is Suitable for applications
that are not time critical and where the response time is not so important
(Embedded systems where missing deadlines are acceptable).
o It is very similar to a conventional procedural programming where the code is
executed task by task
o The tasks are executed in a never ending loop.
o The task listed on top on the program code is executed first and the tasks just
below the top are executed after completing the first task
o A typical super loop implementation will look like:
1. Configure the common parameters and perform initialization for various
hardware components memory, registers etc.
2. Start the first task and execute it
3. Execute the second task
4. Execute the next task
5. :
6. :
7. Execute the last defined task
8. Jump back to the first task and follow the same flow.

The „C‟ program code for the super loop is given below void main ()
{
Configurations (); Initializations ();
while (1)
{
Task 1 ();
Task 2 ();
:
:
Task n ();
}
}

Pros:
 Doesn‟t require an Operating System for task scheduling and monitoring and free from OS
related overheads
 Simple and straight forward design.
 Reduced memory footprint

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 2


Cons:
 Non Real time in execution behavior (As the number of tasks increases, the time intervel
at which a task gets CPU time for execution also increases)
 Any issues in any task execution may affect the functioning of the product (This can be
effectively tackled by using Watch Dog Timers for task execution monitoring)

Enhancements:
 Combine Super loop based technique with interrupts
 Execute the tasks (like keyboard handling) which require Real time attention as Interrupt
Service routines.

2. Embedded firmware Design Approaches – Embedded OS based


Approach:
The embedded device contains an Embedded Operating System which can be one of:
o A Real Time Operating System (RTOS)
o A Customized General Purpose Operating System (GPOS)

 The Embedded OS is responsible for scheduling the execution of user tasks and the
allocation of system resources among multiple tasks
 It Involves lot of OS related overheads apart from managing and executing user
defined tasks
 Microsoft® Windows XP Embedded is an example of GPOS for embedded devices
 Point of Sale (PoS) terminals, Gaming Stations, Tablet PCs etc are examples of
embedded devices running on embedded GPOSs
 „Windows CE‟, „Windows Mobile‟,„QNX‟, „VxWorks‟, „ThreadX‟, „MicroC/OS-II‟,
„Embedded Linux‟, „Symbian‟ etc are examples of RTOSs employed in Embedded
Product development
 Mobile Phones, PDAs, Flight Control Systems etc are examples of embedded devices
that runs on RTOSs

Embedded Firmware Development Languages


1. Assembly Language

2. High Level Language


 Subset of C (Embedded C)
 Subset of C++ (Embedded C++)
 Any other high level language with supported Cross-compiler

3. Mix of Assembly & High level Language


 Mixing High Level Language (Like C) with Assembly Code
 Mixing Assembly code with High Level Language (Like C)
 Inline Assembly

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 3


1. Embedded firmware Development Languages/Options – Assembly
Language
 „Assembly Language‟ is the human readable notation of „machine language‟
 „Machine language‟ is a processor understandable language
 Machine language is a binary representation and it consists of 1s and 0s Assembly
language and machine languages are processor/controller dependent
 An Assembly language program written for one processor/controller family will not
work with others
 Assembly language programming is the process of writing processor specific machine
code in mnemonic form, converting the mnemonics into actual processor instructions
(machine language) and associated data using an assembler
 The general format of an assembly language instruction is an Opcode followed by
Operands
 The Opcode tells the processor/controller what to do and the Operands provide the
data and information required to perform the action specified by the opcode
 It is not necessary that all opcode should have Operands following them. Some of the
Opcode implicitly contains the operand and in such situation no operand is required.
The operand may be a single operand, dual operand or more

Ex: The 8051 Assembly Instruction MOV A, #30


Moves decimal value 30 to the 8051 Accumulator register. Here MOV A is the Opcode and 30
is the operand (single operand). The same instruction when written in machine language will
look like
01110100 00011110
The first 8 bit binary value 01110100 represents the opcode MOV A and the second 8 bit
binary value 00011110 represents the operand 30.
Assembly language instructions are written one per line
A machine code program consists of a sequence of assembly language instructions, where
each statement contains a mnemonic (Opcode + Operand)
Each line of an assembly language program is split into four fields as:
LABEL OPCODE OPERAND ;COMMENTS
LABEL is an optional field. A „LABEL‟ is an identifier used extensively in programs to
reduce the reliance on programmers for remembering where data or code is located. LABEL
is commonly used for representing
 A memory location, address of a program, sub-routine, code portion etc.
o The maximum length of a label differs between assemblers. Assemblers insist
strict formats for labeling. Labels are always suffixed by a colon and begin
with a valid character. Labels can contain number from 0 to 9 and special
character _ (underscore).

;###############################################################
; SUBROUTINE FOR GENERATING DELAY
; DELAY PARAMETR PASSED THROUGH REGISTER R1
; RETURN VALUE NONE, REGISTERS USED: R0, R1
;###############################################################

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 4


DELAY: MOV R0, #255 ;Load Register R0 with 255
DJNZ R1, DELAY ;Decrement R1 and loop till R1= 0
RET ;Return to calling program
 The symbol „;‟ represents the start of a comment. Assembler ignores the text in a line
after the ; symbol while assembling the program
 DELAY is a label for representing the start address of the memory location where the
piece of code is located in code memory
 The above piece of code can be executed by giving the label DELAY as part of the
instruction.
E.g. LCALL DELAY ;LJMP DELAY

Assembly Language – Source File to Hex File Translation:


 The Assembly language program written in assembly code is saved as .asm (Assembly
file) file or a .src (source) file or a format supported by the assembler
 Similar to „C‟ and other high level language programming, it is possible to have multiple
source files called modules in assembly language programming. Each module is
represented by a „.asm‟ or „.src‟ file or the assembler supported file format similar to the
„.c‟ files in C programming
 The software utility called „Assembler‟ performs the translation of assembly code to
machine code
 The assemblers for different family of target machines are different. A51 Macro
Assembler from Keil software is a popular assembler for the 8051 family micro controller

Assembly Language to machine language conversion process

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 5


 Each source file can be assembled separately to examine the syntax errors and incorrect
assembly instructions
 Assembling of each source file generates a corresponding object file. The object file does
not contain the absolute address of where the generated code needs to be placed (a re-
locatable code) on the program memory
 The software program called linker/locater is responsible for assigning absolute address to
object files during the linking process
 The Absolute object file created from the object files corresponding to different source
code modules contain information about the address where each instruction needs to be
placed in code memory
 A software utility called „Object to Hex file converter‟ translates the absolute object file to
corresponding hex file (binary file)

Advantages:
Efficient Code Memory & Data Memory Usage (Memory Optimization):
o The developer is well aware of the target processor architecture and memory
organization, so optimized code can be written for performing operations.
o This leads to less utilization of code memory and efficient utilization of data
memory.
High Performance:
o Optimized code not only improves the code memory usage but also improves the
total system performance.
o Through effective assembly coding, optimum performance can be achieved for
target processor.
Low level Hardware Access:
o Most of the code for low level programming like accessing external device specific
registers from OS kernel ,device drivers, and low level interrupt routines, etc are
making use of direct assembly coding.

Drawbacks:
High Development time:
o The developer takes lot of time to study about architecture, memory organization,
addressing modes and instruction set of target processor/controller.
o More lines of assembly code is required for performing a simple action.
Developer dependency:
o There is no common written rule for developing assembly language based
applications.
Non portable:
o Target applications written in assembly instructions are valid only for that
particular family of processors and cannot be re-used for other target
processors/controllers.
o If the target processor/controller changes, a complete re-writing of the application
using assembly language for new target processor/controller is required.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 6


2. Embedded firmware Development Languages/Options – High Level
Language
 The embedded firmware is written in any high level language like C, C++
 A software utility called „cross-compiler‟ converts the high level language to target
processor specific machine code.
 The cross-compilation of each module generates a corresponding object file. The
object file does not contain the absolute address of where the generated code needs to
be placed (a re-locatable code) on the program memory.
 The software program called linker/locater is responsible for assigning absolute
address to object files during the linking process.
 The Absolute object file created from the object files corresponding to different source
code modules contain information about the address where each instruction needs to
be placed in code memory.
 Software utility called „Object to Hex file converter‟ translates the absolute object file
to corresponding hex file (binary file).

High level language to machine language conversion process

Advantages:
 Reduced Development time: Developer requires less or little knowledge on
internal hardware details and architecture of the target processor/Controller.
 Developer independency: The syntax used by most of the high level languages
are universal and a program written high level can easily understand by a second
person knowing the syntax of the language
 Portability: An Application written in high level language for particular target
processor /controller can be easily be converted to another target
processor/controller specific application with little or less effort

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 7


Drawbacks:
 The cross compilers may not be efficient in generating the optimized target
processor specific instructions.
 Target images created by such compilers may be messy and non- optimized in
terms of performance as well as code size.
 The investment required for high level language based development tools (IDE) is
high compared to Assembly Language based firmware development tools.

3. Embedded firmware Development Languages/Options – Mixing of


Assembly Language with High Level Language
 Embedded firmware development may require the mixing of Assembly Language with
high level language or vice versa.
 Interrupt handling, Source code is already available in high level language\Assembly
Language etc are examples.
 High Level language and low level language can be mixed in three different ways
i. Mixing Assembly Language with High level language like „C‟
ii. Mixing High level language like „C‟ with Assembly Language
iii. In line Assembly
 The passing of parameters and return values between the high level and low level
language is cross-compiler specific

i. Mixing Assembly Language with High level language like ‘C’ (Assembly Language
with ‘C’):
 Assembly routines are mixed with „C‟ in situations where the entire program is written
in „C‟ and the cross compiler in use do not have built in support for implementing
certain features like ISR.
 If the programmer wants to take advantage of the speed and optimized code offered by
the machine code generated by hand written assembly rather than cross compiler
generated machine code.
 For accessing certain low level hardware, the timing specifications may be very
critical and cross compiler generated machine code may not be able to offer the
required time specifications accurately.
 Writing the hardware/peripheral access routine in processor/controller specific
assembly language and invoking it from „C‟ is the most advised method.
 Mixing „C‟ and assembly is little complicated.
 The programmer must be aware of how to pass parameters from the „C‟ routine to
assembly and values returned from assembly routine to „C‟ and how Assembly routine
is invoked from the „C‟ code.
 Passing parameter to the assembly routine and returning values from the assembly
routine to the caller „C‟ function and the method of invoking the assembly routine
from „C‟ code is cross compiler dependent.
 There is no universal written rule for purpose.
 We can get this information from documentation of the cross compiler.
 Different cross compilers implement these features in different ways depending on
GPRs and memory supported by target processor/controller

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 8


ii. Mixing High level language like ‘C’ with Assembly Language (‘C’ with Assembly
Language)
 The source code is already available in assembly language and routine written in a
high level language needs to be included to the existing code.
 The entire source code is planned in Assembly code for various reasons like optimized
code, optimal performance, efficient code memory utilization and proven expertise in
handling the assembly.
 The functions written in „C‟ use parameter passing to the function and returns values
to the calling functions.
 The programmer must be aware of how parameters are passed to the function and how
values returned from the function and how function is invoked from the assembly
language environment.
 Passing parameter to the function and returning values from the function using CPU
registers stack memory and fixed memory.
 Its implementation is cross compiler dependent and varies across compilers.

iii. In line Assembly:


 Inline assembly is another technique for inserting the target processor/controller
specific assembly instructions at any location of source code written in high level
language „C‟
 Inline Assembly avoids the delay in calling an assembly routine from a „C‟ code.
 Special keywords are used to indicate the start and end of Assembly instructions

E.g #pragma asm


Mov A,#13H
#pragma ensasm
 Keil C51 uses the keywords #pragma asm and #pragma endasm to indicate a block of
code written in assembly.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 9


Programming in Embedded C
Whenever the conventional „C‟ Language and its extensions are used for programming embedded
systems, it is referred as „Embedded C programming. Programming in „Embedded C‟ is quite
different from conventional Desktop application development using „C‟ language for a particular
OS platform. Desktop computers contain working memory in the range of Megabytes (Nowadays
Giga bytes) and storage memory in the range of Giga bytes. For a desktop application developer,
the resources available are surplus in quantity and s/he can be very lavish in the usage of RAM
and ROM and no restrictions are imposed at all. This is not the case for embedded application
developers. Almost all embedded systems are limited in both storage and working memory
resources. Embedded application developers should be aware of this fact and should develop
applications in the best possible way which optimizes the code memory and working memory
usage as well as performance. In other words, the hands of an embedded application developer are
always tied up in the memory usage context.

‘C’ vs. ‘Embedded C’


„C‟ is a well structured, well defined and standardized general purpose programming language
with extensive bit manipulation support. „C‟ offers a combination of the features of high level
language and assembly and helps in hardware access programming (system level programming)
as well as business package developments (Application developments like pay roll systems,
banking applications, etc). The conventional „C‟ language follows ANSI standard and it
incorporates various library files for different operating systems. A platform (operating system)
specific application, known as, compiler is used for the conversion of programs written in „C‟ to
the target processor (on which the OS is running) specific binary files. Hence it is a platform
specific development.

Embedded „C‟ can be considered as a subset of conventional „C‟ language. Embedded „C‟
supports all „C‟ instructions and incorporates a few target processor specific
functions/instructions. It should be noted that the standard ANSI „C‟ library implementation is
always tailored to the target processor/controller library files in Embedded „C‟. The
implementation of target processor/controller specific functions/instructions depends upon the
processor/controller as well as the supported cross-compiler for the particular Embedded „C‟
language. A software program called „Cross-compiler‟ is used for the conversion of programs
written in Embedded „C‟ to target processor/controller specific instructions (machine language).

Compiler vs. Cross-Compiler


Compiler is a software tool that converts a source code written in a high level language on top of
a particular operating system running on a specific target processor architecture (e.g. Intel
x86/Pentium). Here the operating system, the compiler program and the application making use of
the source code run on the same target processor. The source code is converted to the target
processor specific machine instructions. The development is platform specific (OS as well as
target processor on which the OS is running). Compilers are generally termed as „Native
Compilers'. A native compiler generates machine code for the same machine (processor) on
which it is running.
Cross-compilers are the software tools used in cross-platform development applications. In cross-
platform development, the compiler running on a particular target processor/OS converts the

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 10


source code to machine code for a target processor whose architecture and instruction set is
different from the processor on which the compiler is running or for an operating system which is
different from the current development environment OS. Embedded system development is a
typical example for cross- platform development where embedded firmware is developed on a
machine with Intel/AMD or any other target processors and the same is converted into machine
code for any other target processor architecture (e.g. 8051, PIC, ARM etc). Keil C51 is an
example for cross-compiler. The term „Compiler‟ is used interchangeably with „Cross-compiler‟
in embedded firmware applications. Whenever you see the term „Compiler‟ related to any
embedded firmware application, please understand that it is referring the cross-compiler.

Using ‘C’ in ‘Embedded C’


The author takes the privilege of assuming the readers are familiar with „C‟ programming.
Teaching „C‟ is not in the scope of this book. If you are not familiar with „C‟ language syntax and
„C‟ programming technique, please get a handle on the same before you proceed.
This section is intended only for giving readers a basic idea on how „C‟ Language is used in
embedded firmware development.
Let us brash up whatever we learned in conventional „C‟ programming. Remember we will only
go through the peripheral aspects and will not go in deep.

Keywords and Identifiers Keywords are the reserved names used by the „C‟ language. All
keywords have a fixed meaning in the „C‟ language context and they are not allowed for
programmers for naming their own variables or functions. ANSI „C* supports 32 keywords and
they are listed below.
All „C‟ supported keywords should be written in „lowercase‟ letters.

Identifiers are user defined names and labels. Identifiers can contain letters of English alphabet
(both upper and lower case) and numbers. The starting character of an identifier should be a letter.
The only special character allowed in identifier is underscore (_).

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 11


Data Types Data type represents the type of data held by a variable. The various data types
supported, their storage space (bits) and storage capacity for „C‟ language are tabulated below.

The data type size and range given above is for an ordinary „C‟ compiler for 32 bit platform. It
should be noted that the storage size may vary for data type depending on the cross-compiler in
use for embedded applications.

Since memory is a big constraint in embedded applications, select the optimum data type for a
variable. For example if the variable is expected to be within the range 0 to 255, declare the same
as an „unsigned char' or „unsigned short int data type instead of declaring it as „int‟ or „unsigned
int‟. This will definitely save considerable amount of memory.

Storage Class Keywords related to storage class provide information on the scope (visibility or
accessibility) and life time (existence) of a variable. „C‟ supports four types of storage classes and
they are listed below.

Apart from these four storage classes, „C‟ literally supports storage class 'global'. An „auto or
static‟ variable declared in the public space of a file (declared before the implementation of all
functions including main in a file) is accessible to all functions within that file. There is no
explicit storage class for „global'. The way of declaration of a variable determines whether it is
global or not.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 12


Arithmetic Operations The list of arithmetic operators supported by „C‟ are listed below

Logical Operations Logical operations are usually performed for decision making and
program control transfer. The list of logical operations supported by „C‟ are listed below

Relational Operations Relational operations are normally performed for decision making and
program control transfer on the basis of comparison. Relational operations supported by „C‟ are listed
below.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 13


Branching Instructions Branching instructions change the program execution flow
conditionally or unconditionally. Conditional branching depends on certain conditions and if the
conditions are met, the program execution is diverted accordingly. Unconditional branching
instructions divert program execution unconditionally.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 14


Looping Instructions Looping instructions are used for executing a particular block of code
repeatedly till a condition is met or wait till an event is fired. Embedded programming often uses
the looping instructions for checking the status of certain I/'o ports, registers, etc. and also for
producing delays. Certain devices allow write/read operations to and from some registers of the
device only when the device is ready and the device ready is normally indicated by a status
register or by setting/clearing certain bits of status registers. Hence the program should keep on
reading the status register till the device ready indication comes. The reading operation forms a
loop. The looping instructions supported by „C‟ are listed below.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 15


Every ‘for’ loop can be replaced by a ‘while’ loop with a counter.

Let‟s consider a typical example for a looping instruction in embedded C application. I have a
device which is memory mapped to the processor and I‟m supposed to read the various registers
of it (except status register) only after the contents of its status register, which is memory mapped
at 0x3000 shows device is ready (say value 0x01 means device is ready). I can achieve this by
different ways as given below.

The instruction char *status_reg = (char*) 0x3000; declares status_reg as a character pointer
pointing to location 0x3000. The character pointer is used since the external device‟s register is
only 8bitwide. We will discuss the pointer based memory mapping technique in a later section. In
order to avoid compiler optimization, the pointer should be declared as volatile pointer.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 16


Arrays and Pointers Array is a collection of related elements (data types). Arrays are usually
declared with data type of array, name of the array and the number of related elements to be
placed in the array. For example the following array declaration declares a character array with
name „arr‟ and reserves space for 5 character elements in the memory as in Figure below

Pointers Pointer is a flexible at the same time most dangerous feature, capable of creating
potential damages leading to firmware crash, if not used properly. Pointer is a memory pointing
based technique for variable access and modification. Pointers are very helpful in
1. Accessing and modifying variables
2. Increasing speed of execution
3. Accessing contents within a block of memory
4. Passing variables to functions by eliminating the use of a local copy of variables
5. Dynamic memory allocation

To understand the pointer concept, let us have a look at the data memory organization of a
processor. For a processor/controller with 128 bytes user programmable internal RAM (e.g.
AT89C51), the memory is organized as:

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 17


Pointer Arithmetic and Relational Operations „C‟ language supports the following Arithmetic
and relational operations on pointers.
1. Addition of integer with pointer, e.g. ptr+2 (It should be noted that the pointer is
advanced forward by the storage length supported by the compiler for the data type of
the pointer multiplied by the integer. For example for integer pointer where storage
size of int = 4, the above addition advances the pointer by 4 x 2 = 8)
2. Subtraction of integer from pointer, e.g. ptr-2 (Above mle is applicable)
3. Incremental operation of pointer, e.g. ++ptr and ptr++ (Depending on the type of
pointer, the ++ increment context varies). For a character pointer ++ operator
increments the pointer by 1 and for an integer pointer the pointer is incremented by the
storage size of the integer supported by the compiler (e.g. pointer ++ results in pointer
+ 4 if the size for integer supported by compiler is 4)
4. Decrement operation of pointer, e.g. —ptr and ptr— (Context rule for decrement
operation is same as that of incremental operation)
5. Subtraction of pointers, e.g. ptrl- ptr2
6. Comparison of two pointers using relational operators, e'.g. ptrl > ptr2, ptrl < ptr2, ptrl
= = ptr2, ptrl! = ptr2 etc (Comparison of pointers of same type only will give
meaningful results)
Note: Addition of two pointers, say ptrl + ptr2 is illegal

Characters and Strings Character is a one byte data type and it can hold values ranging
from 0 to 255 (unsigned character) or -128 to +127 (signed character). The term character literally
refers to the alpha numeric characters (English alphabet A to Z (both small letters and capital
letters) and number representation from „0‟ to „9‟) and special characters like *,?,!, etc. An integer
value ranging from 0 to 255 stored in a memory location can be viewed in different ways. For
example, the hexadecimal number 0x30 when represented as a character will give the character
„0‟ and if it is viewed as a decimal number it will give 48. String is an array of characters. A
group of characters defined within a double quote represents a constant string.
„H‟ is an example for a character, whereas “Hello" is an example for a string. String always
terminates with a „\0‟ character. The „\0‟ character indicates the string termination. Whenever you
declare a string using a character array, allocate space for the null terminator „\0‟; in the array
length.

String operations are very important in embedded product applications. Many of the embedded
products contain visual indicators in the form of alpha numeric displays and are used for
displaying text. Though the conventional alpha numeric displays are giving way to graphic
displays, they are deployed widely in low cost embedded products.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 18


Functions Functions are the basic building blocks of modular programs. A function is a self-
contained and re-usable code snippet intended to perform a particular task. „Embedded C supports
two different types of functions namely, library functions and user defined functions.

Library functions are the built in functions which is either part of the standard „Embedded C‟
library or user created library files. „C‟ provides extensive built in library file support and the
library files are categorized into various types like I/O library functions, string operation library
functions, memory allocation library functions etc. printf(), scanf (), etc. are examples of I/O
library functions. strcpy(), strcmp(), etc. are examples for string operations library functions.
Malloc(), calloc() etc are examples of memory allocation library functions supported by „C‟. All
library functions supported by a particular library is implemented and exported in the same. A
corresponding header („.A‟) file for the library file provides information about the various
functions available to user in a library file. Users should include the header file corresponding to a
particular library file for calling the functions from that library in the „C‟ source file. For example,
if a programmer wants to use the standard I/O library function printf() in the source file, the
header file corresponding to the I/O library file (“ stdio.h ” meant for standard i/o) should be
included in the „c‟ source code using the #include preprocessor directive.

“string.h” is the header file corresponding to the built in library functions for string operations
and “malloc.h” is the header file for memory allocation library' functions. Readers are requested
to get info on header files for other required libraries from the standard ANSI library. As
mentioned earlier, the standard „C‟ library function implementation may be tailored-by cross-
compilers, keeping the function name and parameter list unchanged depending on Embedded „C‟
application requirements. printf() function implemented by C51 cross compiler for 8051
microcontroller is a typical example. The library functions are standardized functions and they
have a unique style of naming convention and arguments. Users should strictly follow-it while
using library functions.

User defined functions are programmer created functions for various reasons like modularity,
easy understanding of code, code reusability, etc. The generic syntax for a function definition
(implementation) is illustrated below.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 19


Return type of a function tells—what is the data type of the value returning by the function on
completion of its execution. The return type can be any of the data type supported by „C‟
language, viz. int, char, float, long, etc. void is the return type for functions which do not return
anything. Function name is the name by which a function is identified. For user defined functions
users can give any name of their interest. For library functions the function name for doing certain
operations is fixed and the user should use those standard function names. Parameters are the list
of arguments to be passed to the function for processing. Parameters are the inputs to the
functions. If a function accepts multiple parameters, each of them are separated using ‟ in the
argument list. Arguments to functions are passed by two means, namely, pass by value and pass
by reference. Pass by value method passes the variables to the function using local copies whereas
in pass by reference variables are passed to the function using pointers. In addition to the above-
mentioned attributes, a function definition may optionally specify the function‟s linkage also. The
linkage of the function can be either „external‟ or „internal ‟.

The task to be executed by the function is implemented within the body of the function. In certain
situations, you may have a single source („.c‟) file containing the entire variable list, user defined
functions, function main(), etc. There are two methods for writing user defined functions if the
source code is a single „.c‟ file. The first method is writing all user defined functions on top of the
main function and other user defined functions calling the user defined function.

If you are writing the user defined functions after the entry' function main() and calling the same
inside main, you should specify the function prototype (function declaration) of each user defined

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 20


functions before the function main(). Otherwise the compiler assumes that the user defined
function is an extern function returning integer value and if you define the function after main()
without using a function declaration/prototype, compiler will generate error complaining the user
defined function is redefining (Compiler already assumed the function which is used inside
main() without a function prototype as an extern function returning an integer value). The
function declaration informs the compiler about the format and existence of a function prior to its
use. Implicit declaration of functions is not allowed: every function must be explicitly declared
before it is called. The general form of function declaration is

The „Linkage Type‟ specifies the linkage for the function. It can be either „external‟ or „internal‟.
The „static‟ keyword for the „ Linkage Type‟ specifies the linkage of the function as internal
whereas the „extern‟ „Linkage Type' specifies „external‟ linkage for the function. It is not
mandatory to specify the name of the argument along with its type in the argument list of the
function declaration. The declarations can simply specify the types of parameters in the argument
list. This is called function prototyping. A function prototype consists of the function return type,
the name of the function, and the parameter list. The usage is illustrated below.

Function Pointers A function pointer is a pointer variable pointing to a function. When an


application is compiled, the functions which are part of the application are also get converted into
corresponding processor/compiler specific codes. When the application is loaded in primary
memory for execution, the code corresponding to the function is also loaded into the memory and
it resides at a memory address provided by the application loader. The function name maps to an
address where the first instruction (machine code) of the function is present. A function pointer
points to this address.

The general form of declaration of a function pointer is given below.

Where, „return_type‟ represents the return type of the function, „pointer_name‟ represents the
name of the pointer and „argument list‟ represents the data type of the arguments of the function.
If the function contains multiple arguments, the data types are separated using “,”. Typical
declarations of function pointer are given below.

The parentheses () around the function pointer variable differentiates it as a function pointer
variable. If no parentheses are used, the declaration will look like

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 21


Structures and Unions „structure‟ is a variable holding a collection of data types (int, float,
char, long etc). The data types can be either unique or distinct. The tag „struct‟ is used for
declaring a structure. The general form of a structure declaration is given below.

struct_name is the name of the structure and the choice of the name is left to the programmer.
Let us examine the details kept in an employee record for an employee in the employee database
of an organization as example. A typical employee record contains information like „Employee
Name', „Employee Code‟, and „Date of Birth‟. This information can be represented in „C‟ using a
structure as given below.

Unions Union is a concept derived from structure and union declarations follow the same syntax
as that of structures (structure tag is replaced by union tag). Though union looks similar to
structure in declaration, it differs from structure in the memory allocation technique for the
member variables. Whenever a union variable is created, memory is allocated only to the member
variable of union requiring the maximum storage size. But for structures, memory is allocated to
each member variables. This helps in efficient data memory usage. Even if a union variable
contains different member variables of different data types, existence is only for a single variable
at a time. The size of a union returns the storage size of its member variable occupying the
maximum storage size. The syntax for declaring union is given below:

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 22


'union_name‟ is the name of the union and programmers can use any name according to their
programming style.

1. The offsetof() macro returns the offset of a member variable (in bytes) from the beginning
of its parent structure. The usage is offsetof (structName, memberName); where
„structName‟ is the name of the parent structure and „ memberName ‟ is the name of the
member in the parent data structure whose offset is to be determined. For using this macro
use the header file „ stddef.h ‟
2. If you declare a structure just before the main () function in your source file, ensure that
the structure is terminated with the structure definition termination indicator Otherwise
function main() will be treated as a structure and the application may crash with
exceptions.
3. A union variable can be initialized at the time of creation with the first member variable
value only.

Pre-processors and Macros Pre-processor in „C‟ is compiler/cross-compiler directives used


by compiler/cross-compiler to filter the source code before compilation/cross-compilation. The
pre-processor directives are mere directions to the compilers/cross compilers on how the source
file should be compiled/cross compiled. No executable code is generated for pre-processor
directives on compilation. They are same as pseudo ops in assembly language. Pre-processors are
very useful for selecting target processor/controller dependent pieces of code for different target
systems and allow a single source code to be compiled and run on several different target boards.
The syntax for pre-processor directive is different from the syntax of C language. Each pre-
processor directive starts with the „#‟ symbol and ends without a semicolon (;). Pre-processor
directives are normally placed before the entry point function main() in a source file. Pre-
processor directives are grouped into three categories; namely

1. File inclusion pre-processor directives


2. Compile control pre-processor directives
3. Macro substitution pre-processor directives

Macro substitution pre-processor directives Macros are a means of creating portable inline code.
„Inline‟ means wherever the macro is called in a source file it is replaced directly with the code
defined by the macro. In-line code improves the performance in terms of execution speed. Macros
are similar to subroutines in functioning but they differ in the way in which they are coded in the
source code. Functions are normally written only once with arguments. Whenever a function
needs to be executed, a call to the function code is made with the required parameters at the point
where it needs to be invoked. If a macro is used instead of functions, the compiler inserts the code
for macro wherever it is called. From a code size viewpoint macros are non-optimized compared
to functions. Macros are generally used for coding small functions requiring execution without
latency. The „#define‟ pre-processor directive is used for coding macros. Macros can be either
simple definitions or functions with arguments.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 23


#define PI 3.1415 is an example for a simple macro definition.
Macro definition can contain arithmetic operations also. Proper care should be taken in giving
right syntax while defining macros, keeping an eye on their usage in the source code. Consider
the following example
#define A 12+25
#define B 45-10

Constant Declarations in Embedded ‘C’ In embedded applications the qualifier


(keyword) „const‟ represents a „Read only‟ variable. Use of the keyword „const‟ in front of a
variable declares that the value of the variable cannot be altered by the program. This is a kind of
defensive programming in which any attempt to modify a variable declared as „const‟ is reported
as an access violation by the cross-compiler. The different types of constant variables used in
embedded „C‟ application are explained below.

Constant data Constant data informs that the data held by a variable is always constant and
cannot be modified by the application. Constants used as scaling variables, ratio factors, various
scientific computing constants (e.g. Plank‟s constant), etc. are represented as constant data. The
general form of declaring a constant variable given below.

„const‟ is the keyword informing compiler/cross compiler that the variable is constant, „data type‟
gives the data type of the variable. It can be „int‟, „char‟, „float‟, etc. „variable name‟ is the
constant variable.
const float PI = 3.1417;
float const PI = 3.1417;

The ‘Volatile’ Type Qualifier in Embedded „C‟ The keyword „ volatile ‟ prefixed with any
variable as qualifier informs the cross-compiler that the value of the variable is subject to change
at any point of time (subject to asynchronous modification) other than the current statement of
code where the control is at present.
1. Variables common to Interrupt Service Routines (ISR) and other functions of a file.
2. Memory mapped hardware registers.
3. Variables shared by different threads in a multi threaded application (Not applicable to Super
loop Firmware development approach)
The „volatile‟ keyword informs the cross-compiler that the variable with „volatile‟ qualifier is
subject to asynchronous change and there by the cross compiler turns off any optimization
(assumptions on the variable) for these variables. The general form of declaring a volatile variable
is given below.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 24


„data type‟ refers to the data type of the variable. It can be int, char, float, etc. „variable name‟ is
the user defined name for the volatile variable of the specified type.

Delay Generation and Infinite Loops in Embedded C Almost every embedded


application involves delay programming. Embedded applications employ delay programming for
waiting for a fixed time interval till a device is ready, for inserting delay between displays
updating to give the user sufficient time to view the contents displayed, delays involved in bit
transmission and reception in asynchronous serial transmissions like I2C, 1-Wire data transfer,
delay for key de-bouncing etc. Some delay requirements in embedded application may be critical,
meaning delay accuracy should be within a very narrow tolerance band. Typical example is delay
used in bit data transmission. If the delay employed is not accurate, the bits may lost while
transmission or reception. Certain delay requirements in embedded application may not be much
critical, e.g. display updating delay.
It is easy to code delays in desktop applications under DOS or Windows operating systems. The
library function delay() in DOS and Sleep() in Windows provides delays in milliseconds with
reasonable accuracy. Coding delay routines in embedded applications is bit difficult. The major
reason is delay is dependent on target system‟s clock frequency. So we need to have a trial and
error approach to code delays demanding reasonably good accuracy. Delay codes are generally
non-portable. Delay routine requires a complete re-work if the target clock frequency is changed.
Normally „for loops' are used for coding delays. Infinite loops are created using various loop
control instructions like while (), do while (), for and goto labels. The super loop created by while
(1) instruction in a traditional super loop based embedded firmware design is a typical example
for infinite loop in embedded application development.

Infinite loop using while The following code snippet illustrates „while‟ for infinite loop
implementation.

Infinite loop using do while

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 25


Infinite loop using for

Infinite loop using goto

Which technique is the best? According to all experienced Embedded „C‟ programmers while()
loop is the best choice for creating infinite loops. There is no technical reason for this. The clean
syntax of while loop entitles it for the same. The syntax of for loop for infinite loop is little
puzzling and it is not capable of conveying its intended use. „goto‟ is the favorite choice of
programmers migrating from Assembly to Embedded C ©.
break; statement is used for coming out of an infinite loop. You may think why we implement an
infinite loop and then quitting it? Answer - There may be instructions checking some condition
inside the infinite loop. If the condition is met the program control may have to transfer to some
other location.

Bit Manipulation Operations Though Embedded „C‟ does not support a built in Boolean
variable (Bit variable) for holding a „TRUE (Logic 1)‟ or „FALSE (Logic 0)‟ condition, it
provides extensive support for Bit manipulation operations. Boolean variables required in
embedded application are quite often stored as variables with least storage memory requirement
(obviously char variable). Indeed it is wastage of memory if the application contains large number
of Boolean variables and each variable is stored as a char variable. Only one bit (Least Significant
bit) in a char variable is used for storing Boolean information. Rest 7 bits are left unused. This
will definitely lead to serious memory bottle neck. Considerable amount of memory can be saved
if different Boolean variables in an application are packed into a single variable in „C‟ which
requires less memory storage bytes. A character variable can accommodate 8 Boolean variables.
If the Boolean variables are packed for saving memory, depending upon the program requirement
each variable may have to be extracted and some manipulation (setting, clearing, inverting, etc.)
needs to be performed on the bits. The following Bit manipulation operations are employed for
the same.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 26


Bitwise AND Operator „&‟ performs Bitwise AND operations. Please note that the Bitwise AND
operator is entirely different from the Logical AND operator. The „&‟ operator acts on individual
bits of the operands. Bitwise AND operations are usually performed for selective clearing of bits
and testing the present state of a bit (Bitwise ANDing with „ 1 ‟).

Bitwise OR Operator „| ‟ performs Bitwise OR operations. Logical OR operator „||‟ is in no way


related to the Bitwise OR operator „| \ Bitwise OR operation is performed on individual bits of the
operands. Bitwise OR operation is usually performed for selectively setting of bits and testing the
current state of a bit (Bitwise ORing with „0‟).

Bitwise Exclusive OR- XOR Bitwise XOR operator „^‟ acts on individual operand bits and
performs an „Excusive OR‟ operation on the bits. Bitwise XOR operation is used for toggling bits
in embedded applications.

Bitwise NOT Bitwise NOT operations negates (inverts) the state of a bit. The operator (tilde) is
used as the Bitwise NOT operator in C.

Setting and Clearing and Bits Setting the value of a bit to „1‟ is achieved by a Bitwise OR
operation. For example consider a character variable (8bit variable) flag. The following
instruction sets its 0th bit always 1.

Toggling bits
Toggling a bit is performed to negate (toggle) the current state of a bit. If current state of a
specified bit is „1 ‟, after toggling it becomes „0‟ and vice versa. Toggling is also known as
inverting bits. The Bitwise XOR operator is used for toggling the state of a desired bit in an
operand.

Extracting and inserting bits


Quite often it is meaningful to store related information as the bits of a single variable instead of
saving them as separate variables. This saves considerable amount of memory in an embedded
system where data memory is a big bottleneck. Typical scenario in embedded applications where
information can be stored using the bits of single variable is, information provided by Real Time
Clock (RTC). RTC chip provides various data like current date/month/year, day of the week,
current time in hours/minutes/ seconds, etc. If an application demands the storage of all these
information in the data memory of the embedded system for some reason, it can be achieved in
two ways;
1. Use separate variables for storing each data (date, month, year, etc.)
2. Club the related data and-store them as the bits of a single variable

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 27


As an example assume that we need to store the date information of the RTC in the data memory
in D/M/Y format. Where „D‟ stands for date varying from 1 to 31, „M‟ stands for month varying
from 1 to 12 and „Y‟ stands for year varying from 0 to 99. If we follow the first approach we need
3 character variables to store the date information separately. In the second approach, we need
only 5 bits for date (With 5 bits we can represent 0 to 2 5 -1 numbers (0 to 31), 4 bits for month
and 7 bits for year. Hence the total number of bits required to represent the date information is 16.
All these bits can be fitted into a 16 bit variable as shown in Fig

Testing bits
So far we discussed the Bitwise operators for changing the status of a selected bit. Bitwise
operators can also be used for checking the present status of a bit without modifying it for
decision making operations.

This instruction examines the status of the 6 th bit of variable flag. The same can also be achieved
by using a constant bit mask as

Coding Interrupt Service Routines (ISR) Interrupt is an event that stops the current
execution of a process (task) in the CPU and transfers the program execution to an address in
code memory where the service routine for the event is located. The event which stops the current
execution can be an internal event or an external event or a proper combination of the external
and internal event. Any trigger signal coming from an externally interfaced device demanding
immediate attentions is an example for external event whereas the trigger indicating the overflow
of an internal timer is an example for internal event. Reception of serial data through the serial
line is a combination of internal and external events. The number of interrupts supported, their
priority levels, interrupt trigger type and the structure of interrupts are processor/controller
architecture dependent and it varies from processor to processor. Interrupts are generally
classified into two: Maskable Interrupts and Non-maskable Interrupts (NMI). Maskable interrupt
can be ignored by the CPU if the interrupt is internally not enabled or if the CPU is currently
engaged in processing another interrupt which is at high priority. Non-maskable interrupts are
interrupts which require urgent attention and cannot be ignored by the CPU. Reset (RST) interrupt
and TRAP interrupt of 8085 processor are examples for Non-maskable interrupts.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 28


1. Save the current context (Important Registers which the ISR will modify)
2. Service the Interrupt
3. Retrieve the saved context (Retrieve the original contents of registers)
4. Return to the execution point where the execution is interrupted

Normal functions will not incorporate code for saving the current context before executing the
function and retrieve the saved context before exiting the function. ISR should incorporate code
for performing operations.
Current context saving instructions are written on top of the call to the „C‟ function and context
retrieving instructions are written just below the „C‟ function call. It is little puzzling to find
answers to the following questions in this approach.

1. Which registers must be saved and restored since we are not sure which registers are used by
the cross compiler for implementing the „C‟ function?
2. How the assembly instructions can be interfaced with high-level language like „C‟?

Answers to these questions are cross compiler dependent and you need to find the answer by
referring the documentation files of the cross-compiler in use.

Recursive Functions A function which calls itself repeatedly is called a Recursive Function.
Using recursion, a complex problem is split into its single simplest form. The recursive function
only knows how to solve that simplest case. Recursive-functions are useful in evaluating certain
types of mathematical function, creating and accessing dynamic data structures such as linked
lists or binary trees.

Recursion vs. Iteration: A comparison, both recursion and iteration is used for implementing
certain operations which are self repetitive in some form.
 Recursion involves a lot of call stack overhead and function calls. Hence it is slower in
operation compared to the iterative method. Since recursion implements the functionality
with repeated self function calls, more stack memory is required for storing the local
variables and the function return address
 Recursion is the best method for implementing certain operations like certain
mathematical operation, creating and accessing of dynamic data structures such as linked
lists or binary trees
 A recursive solution implementation can always be replaced by iteration. The process of
converting a recursive function to iterative method is called „unrolling‟

Benefits of Recursion: Recursion brings the following benefits in programming:


 Recursive code is very expressive compared to iterative code. It conveys the intended use.
 Recursion is the most appropriate method for certain operations like permutations, search
trees, sorting, etc.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 29


Drawbacks of Recursion: Though recursion is an effective technique in implementing solutions
expressed in terms of a successive application of the, same solution to the solution subsets, it
possesses the following drawbacks.
 Recursive operation is slow in operation due to call stack overheads. It involves lot of
stack operations like local variable storage and retrieval, return address storage and
retrieval, etc.
 Debugging of recursive functions are not so easy compared to iterative code debugging.

Dynamic Memory Allocation Every embedded application, regardless of whether it is


running on an operating system based product or a non-operating system based product (Super
loop based firmware Architecture) contains different types of variables and they fall into any one
of the following storage types namely; static, auto, global or constant data. Regardless of the
storage type each variable requires memory locations to hold them physically. The storage type
determines in which area of the memory each variable needs to be kept. For an Assembly
language programmer, handling memory for different variables is quite difficult. S/he needs to
assign a particular memory location for each variable and should recollect where s/he kept the
variable for operations involving that variable.

Certain category of embedded applications deal with fixed number of variables with fixed length
and certain other applications deal with variables with fixed memory length as well as variable
with total storage size determined only at the runtime of application (e.g. character array with
variable size). If the number of variables are fixed in an application and if it doesn‟t require a
variable size at run time, the cross compiler can determine the storage memory required by the
application well in advance at the run time and can assign each variable an absolute address or
relative (indirect) address within the data memory. Here the memory required is fixed and
allocation is done before the execution of the application. This type of memory allocation is
referred as „Static Memory Allocation'. The term „Static‟ mentioned here refers 'fixed‟ and it is no
way related to the storage class static. As mentioned, some embedded applications require data
memory which is a combination of fixed memory (Number of variables and variable size is
known prior to cross compilation) and variable length data memory. As an example, let‟s take the
scenario where an application deals with reading a stream of character data from an external
environment and the length of the stream is variable. It can vary between any numbers (say 1 to
100 bytes). The application needs to store the data in data memory temporarily for some
calculation and it can be ignored after the calculation. This scenario can be handled in two ways
in the application program. In the first approach, allocate fixed memory with maximum size (say.
100 bytes) for storing the incoming data bytes from the stream. In the second approach allocate
memory at run .time of the application and de-allocate (free) the memory once the data memory
storage requirement is over. In the first approach if the memory is allocated fixedly, it is locked
forever, and cannot re-used by the application even if there is no requirement for the allocated
number of bytes and it will definitely create memory bottleneck issues in embedded systems
where memory is a big constraint. Hence it is not advised to go for fixed memory allocations for

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 30


applications demanding variable memory size at run time. Allocating memory on demand and
freeing the memory at run time is the most advised method for handling the storage of dynamic
(changing) data and this memory allocation technique is known as „Dynamic Memory Allocation‟

Dynamic memory allocation technique is employed in Operating System (OS) based embedded
systems. Operating system contains a „Memory Management Unit‟ and it is responsible for
handling memory allocation related operations. The memory management unit allocates memory
to hold the code for the application and the variables associated with the application. The
conceptual view of storage of an application and the variables related to the application is
represented in Figure below

Memory manager allocates a memory segment to each application and the memory segment holds
the source code of the application as well as data related to the application. The actual program
code (executable machine code instructions) is stored at the lower address of the memory (at the
beginning address of the memory segment and stores upward). Constant data related to the
application (e.'g. const int x = 10;) is stored at the memory area just above the executable code.
The alterable data (global and static variables; e.g. static int j = 10; int k= 2; (global declaration),
etc.) are stored at the „Alterable Data ' memory area. The size of the executable code, the number
of constant data and alterable data in an application is always fixed and hence they occupy only a
fixed portion of the memory segment. Memory allocated for the executable code, constant data
and alterable data together constitute the 'Static storage memory (Fixed storage memory)'. The
storage memory available within the memory segment excluding the fixed memory is the
„Dynamic storage memory'. Dynamic storage memory area is divided into two separate entities
namely 'stack' and 'heap'. 'Stack memory' normally starts at the high memory-area (At the top

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 31


address) of the memory segment and grows down. Within a memory segment, fixed number of
storage bytes is allocated to stack memory and the stack can grow up to this maximum allocated
size. Stack memory is usually used for holding auto variables (variables local to a function),
function parameters and function return values. Depending upon the function calls/rectum and
auto variable storage, the size of the stack grows and shrinks dynamically. If at any point of
execution the stack memory exceeds the allocated maximum storage size, the application may
crash and this condition is called „Stack overflow'. The free memory region lying in between the
stack and fixed memory area is called 'heap'. Heap is the memory pool where storage for data is
done dynamically as and when the application demands. Heap is located immediately above the
fixed memory area and it grows upward. Any request for dynamic memory allocation by the
program increases the size of heap (depending on the availability of free memory within heap)
and the free memory request decrements the size of heap (size of already occupied memory
within the heap area). Heap can be viewed as a „bank‟ in real life, application, where customers
can demand for loan. Depending on the availability of money, bank may allot loan to the
customer and customer re-pays the loan to the bank when he/she is through with his/her need.
Bank uses the money repaid by a customer to allocate a loan to another customer—some kind of
rolling mechanism. „C‟ language establishes the dynamic memory allocation technique through a
set of „Memory management library routines'. The most commonly used „C‟ library functions for
dynamic memory allocation are „malloc‟, „calloc‟, „realloc‟ and „free' . The following sections
illustrate the use of these memory allocation library routines for allocating and de-allocating
(freeing) memory-dynamically.

malloc(): malloc() function allocates a block of memory dynamically. The malloc() function
reserves a block of memory of size specified as parameter to the function, in the heap memory
and returns a pointer of type void. This can be assigned to a pointer of any valid type. The general
form of using malloc() function is given below.

where „pointer' is
a pointer of type „pointer_type‟. „pointer_type‟ can be 'int', 'char', 'float' etc. malloc() function
returns a pointer of type „pointer_type‟ to a block of memory with size 'no. of bytes‟. A typical
example is given below.

This instruction allocates 50 bytes (If there is 50 bytes of memory available in the heap area) and
the address of the first byte of the allocated memory in the heap area is assigned to the pointer ptr
of type char. It should be noted that the malloc() function allocates only the requested number of
bytes and it will not allocate memory automatically for the units of the pointer in use. For
example, if the programmer wants to allocate memory for 100 integers dynamically, the following
code

will not allocate memory size for 100 integers, instead it allocates memory for just 100 bytes. In
order to make it reserving memory for 100 integer variables, the code should be re-written as

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 32


malloc() function can also be used for allocating memory for complex data types such as structure
pointers apart from the usual data types like „int‟, „char‟, „float‟ etc. malloc() allocates the
requested bytes of memory in heap in a continuous fashion and the allocation fails if there is no
sufficient memory available in continuous fashion for the requested number of bytes by the
malloc() functions. The return value of malloc() will be NULL (0) if it fails. Hence the return
value of malloc() should be checked to ensure whether the memory allocation is successful before
using the pointer returned by the malloc() function.

Remember malloc() only allocates required bytes of memory and will not initialise the allocated
memory. The allocated memory contains random data.

calloc() The library function calloc() allocates multiple blocks of storage bytes and initialises each
allocated byte to zero. Syntax of calloc() function, is illustrated below.

where „pointer‟ is a pointer of type „pointer_type‟. „pointer_type‟ can be „int‟, „char‟, „float‟ etc.
„n‟ stands for the number of blocks to be allocated and „size of block‟ tells the size of bytes
required per block. The calloc(n, size of block) function allocates continuous memory for „n‟
number of blocks with „size of block‟ number of bytes per block and returns a pointer of type
„pointer_type‟ pointing to the first byte of the allocated block of memory. A typical example is
given below.

Above instruction allocates 50 contiguous blocks of memory each of size one byte in the heap
memory and assign the address of the first byte of the allocated memory region to the character
pointer „ptr'. Since malloc() is capable of allocating only fixed number of bytes in the heap area
regardless of the storage type, calloc() can be used for overcoming this limitation as discussed
below.

This instruction allocates 50 blocks of memory, each block representing an integer variable and
initializes the allocated memory to zero. Similar to the malloc() function, calloc() also returns a
„NULL‟ pointer if there is not enough space in the heap area for allocating storage for the
requested number of memory by the calloc() function. Hence it is advised to check the pointer to
which the calloc() assigns the address of the first byte in the allocated memory area. If calloc()
function fails, the return value will be „NULL‟ and the pointer also will be „NULL‟. Checking the

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 33


pointer for „NULL‟ immediately after assigning with pointer returned by calloc() function avoids
possible errors and application crash. Features provided by calloc() can be implemented using
malloc() function as

For example

The function memset (ptr, 0, n * size of. block) sets the memory block of size (n * size of block)
with starting address pointed by „ptr‟ to zero.

free() The „C‟ memory management library function free() is used for releasing or de-allocating
the memory allocated in the heap memory by malloc() or calloc() functions. If memory is
allocated dynamically, the programmer should release it if the dynamically allocated memory is
no longer required for any operation. Releasing the dynamically allocated memory makes it ready
to use for other dynamic allocations. The syntax of free() function is given below.

„ptr‟ is the valid pointer returned by the calloc() or malloc() function on dynamic memory
allocation. Use of an invalid pointer with function free() may result in the unexpected behaviour
of the application.

Note:
1. The dynamic memory allocated using malloc() or calloc() functions should be released
(deallocated)
2. Any use of a pointer which refers to freed memory space results in abnormal behaviour of
application:
3. If the parameter to free() function is not a valid pointer, the application behaviour may be
unexpected.

realloc() realloc() function is used for changing the size of allocated bytes in a dynamically
allocated memory block. You-may come across situations where the allocated memory is not
sufficient to hold the required data or it is surplus in terms of allocated memory bytes. Both of
these situations are handled using realloc() function. The realloc() function changes the size of the
block of memory pointed to, by the pointer parameter to the number of bytes specified by the-
modified size parameter and it returns a new pointer to the block. The pointer specified by the
pointer parameter must have been created with the malloc, calloc, or realloc subroutines and not
been de-allocated with the free or realloc subroutines. Function realloc() may shift the position of
the already allocated block depending on the new size, with preserving the contents of the already

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 34


allocated block and returns a pointer pointing to the first byte of the re-allocated memory block.
realloc() returns a void pointer if there is sufficient memory available for allocation, else return a
„NULL‟ pointer. Syntax of realloc is given below.

Example illustrating the use of realloc() function is given below.

Vikas Kumar Tiwari, Assistant Professor, Dept. of ECE, VITS Page 35

You might also like