Ees04 C
Ees04 C
Ees04 C
Introductory
Elements
Levels of Abstraction
Problems
Algorithms
Language
Machine (ISA) Architecture
Microarchitecture
Circuits
Devices
Transistors
Logic gates, multiplexers, memory, etc.
LC-3 Architecture
Machine code
Assembly code
High Level Languages
P.Roper
- 3 -
Compilation vs. Interpretation
Interpretation: An interpreter reads the program and performs the
operations in the program
The program does not execute directly, but is executed by the
interpreter.
Compilation: A compiler translates the program into a machine language
program called an executable image.
The executable image of the program directly executes on the
hardware.
The interpreter and compiler are themselves programs
P.Roper
- 4 -
Interpretation
Algorithm
High-Level Language Program
c = a + b;
by hand
Interpreter
read &
execute
program text
P.Roper
- 5 -
Interpretation
Program code is interpreted at runtime
lines of code are read in
interpreter determines what they represent
requested function is performed
interpret()
{
while(1) // do forever
{
readInputLine();
if(line[0..3] == "mult")
doMultiply();
else if(line[0..2] == "add")
doAdd();
else ...
}
}
Interpretation is common:
+ LISP
+ BASIC
+ Perl
+ Java
+ Matlab
+ LC-2 simulator
+ UNIX shell
+ MS-DOS command line
Interpretation can be slow...
P.Roper
- 6 -
Compilation
Algorithm
C-language program
c = a + b;
by hand
Machine language programs
0010110010010001111
assembler
Assembly language program
ADD r0,r1,r2
compiler
to machine for execution
The assembly language stage
is often skipped
Compiler often directly
generates machine code.
P.Roper
- 7 -
Compilation
Compilers convert high-level code to machine code
compile once, execute many times
resulting machine code is optimized
may include intermediate step (assembly)
slower translation, but higher performance when executed
Is an assembler considered a compiler?
assemblers do convert higher level code to machine code, but
they are usually in a class by themselves
P.Roper
Why C
This is NOT a C course.
Why C:
Small size
Loose typing
External standard library I/O, other facilities
Low level (BitWise) programming readily available
Pointer implementation - extensive use of pointers for memory, array,
structures and functions
Macro preprocessor
It can handle low-level activities
It produces efficient programs
It can be compiled on a variety of computers
What C is used for
Systems programming:
OSes, like Linux
microcontrollers: automobiles and airplanes
Wireless Sensor Networks
embedded processors: phones, portable electronics, etc.
DSP processors: digital audio and TV systems
Embedded systems
Smart phones (C derivatives)
Compilers and tools
GPU ( + Extensions)
- 10 -
Compiling a C Program
C Source Code
C Preprocessor
Library & Object
Files
Executable
Image
C Compiler
Preprocessed
source code
Source Code
Analysis
1
st
Pass
Symbol
Table
Code
Generation
2
nd
Pass
Linker
Object module
P.Roper
Bitwise Operators
The combination of pointers and bit-level operators makes C useful for many
low level applications and can almost replace assembly code
Writing software for embedded systems requires the manipulation of data at
addresses, and this requires manipulating individual bits or groups of bits.
~ (Bitwise Negation)
& (AND Operator)
a b ~a ~b
0 1 1 0
0x0F0F 0x0000 0xF0F0 0xFFFF
a b a & b
1 0 0
1 1 1
0xBEEF 0xFFF0 0xBEE0 Mask (clear) bits
Bitwise Operators
| (OR Operator)
^ (XOR Operator)
a b a | b
1 1 1
0 1 1
0x0EAD 0xD000 0xDEAD Mask (set) bits
a b a ^ b
1 1 0
0 1 1
0xF0FA 0xFF00 0x0FFA Toggling bits
Bitwise Operators
Shifting Bits ( << left shift, >> right shift)
unsigned int i = 0xF0F0; // 1111 0000 1111 0000
unsigned int left = 0xF0F0 | (1 << 10); // 1111 0100 1111 0000
set the 11th bit
unsigned int i = 0x0F0F; // 0000 1111 0000 1111
unsigned int left = 0xF0F0 & (~(1 << 10)); // 0000 1011 0000 1111
clear the 11th bit
Why pointers?
allow different section of code to share information easily
enable complex data structures like linked list and binary trees
programs can manipulate addresses directly
A pointed doesnt store a simple value directly. Instead it store a reference, an
address of something else
&expr evaluates to the address of the location expr
*expr evaluates to the value stored in the address expr
Pointers
num
numptr
25 A simple int variable
A pointer variable. The current
value is a reference to the num
Pointer size depends on address space, not type
When a pointer is first allocated it doesnt have a pointee. It points to a random
memory address. Every pointer starts out with a bad value: each pointer must be
assigned a pointee before it can support deference operations
int foo;
int *x;
foo = 123;
x = &foo;
Pointers
Memory Addresses
0
1
2
...
23
24
foo -------- 123
1 x --------
numptr 0x???? 0x???? (data overwrites, segfault, ...)
Pointers
int num = 3;
int *num_ptr = NULL;
num_ptr = #
num = 5;
*num_ptr = 7;
int x = *num_ptr;
num_ptr num
NULL 3
num_ptr num
3
num_ptr num
5
num_ptr num
7
num_ptr num
7
x
7
Pointers
int *y_ptr = num_ptr;
*y_ptr = 6;
y_ptr = &x;
num_ptr num
7
x
7
y_ptr
num_ptr num
6
x
7
y_ptr
num_ptr num
6
x
7
y_ptr
Pointers
int **p_ptr_ptr;
p_ptr_ptr = &num_ptr;
**p_ptr_ptr = 5;
num_ptr num
7
x
7
y_ptr
p_ptr_ptr
num_ptr num
5
x
7
y_ptr
p_ptr_ptr
Integer math operations can be used with pointers
If you increment (or decrement) a pointer, it will be increased by the size of
whatever it points to
Pointers are passed by value (the value of a pointer is the address it holds)
int a[5];
int *ptr = a;
void swap(int *x, int *y){
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
Pointers
a[0] a[1] a[2] a[3] a[4]
*ptr *(ptr + 2) *(ptr + 4)
A useful technique is the ability to have pointers to functions
int (*func)(int a, float b);
Once you've got the pointer, you can assign the address of the right sort of
function just by using its name: a function name is turned into an address when
it's used in an expression
void func(int);
main(){
void (*fp)(int);
fp = func;
(*fp)(1);
}
void func(int){
...
}
If you like writing finite state machines, you might like to know that you can have
an array of pointers to functions
void (*fparr[])(int, float);
Pointers
In computer science , a library is a collection of subroutines or
classes used to develop software
Libraries contain code and data that provide services to independent
programs. This allows the sharing and changing of code and data in
a modular fashion
Executables and libraries make references known as links to each
other through the process known as linking, which is typically done
by a linker
Two types of libraries:
Static libraries
Dynamic / shared libraries
Libraries
Static libraries are simply a collection of ordinary object files;
conventionally, static libraries end with the .a suffix
Static libraries aren't used as often as they once were, because of
the advantages of shared libraries
But in embedded
Static libraries permit users to link to programs without having to
recompile its code, saving recompilation time
Static libraries are often useful for developers if they wish to permit
programmers to link to their library, but don't want to give the library
source code
For example, the C math library is typically stored in the file
/usr/lib/libm.a on Unix-like systems.
The corresponding prototype declarations for the functions in this
library are given in the header file /usr/include/math.h (lib + header)
Libraries (static)
Here is an example program which makes a call to the external
function sqrt in the math library libm.a
#include <math.h>
#include <stdio.h>
Int main (void){
double x = sqrt (2.0);
printf ("The square root of 2.0 is %f\n", x);
return 0;
}
To enable the compiler to link the sqrt function to the main program
calc.c we need to supply the library libm.a
gcc -Wall calc.c /usr/lib/libm.a -o calc
gcc -Wall calc.c -lm -o calc
Libraries (static)
Shared libraries are libraries that are loaded by programs when they
start
It's actually much more flexible and sophisticated than this, because
the approach used by Linux permits you to:
update libraries and still support programs that want to use older,
non-backward -compatible versions of those libraries override
specific libraries or even specific functions in a library when
executing a particular program
do all this while programs are running using existing libraries
If the linker finds that the definition for a particular symbol is in a
shared library, then it doesn't include the definition of that symbol in
the final executable
Instead, the linker records the name of symbol and which library it is
supposed to come from in the executable file instead
Libraries (dynamic)
When the program is run, the operating system arranges that these remaining
bits of linking are done "just in time" for the program to run. Before the main
function is run, a smaller version of the linker (often called ld.so) goes through
these promissory notes and does the last stage of the link there and then
pulling in the code of the library and joining up all of the dots
If a particular symbol is pulled in from a particular shared library (say printf in
libc.so), then the whole of that shared library is mapped into the address space
of the program. This is very different from the behavior of a static library, where
only the particular objects that held undefined symbols got pulled in
Compiling for runtime linking with a dynamically linked libctest.so.1.0:
gcc -Wall -L/opt/lib prog.c -lctest -o prog
The libraries will NOT be included in the executable but will be dynamically
linked during runtime execution
Libraries (dynamic)
CPU operates on data in a handful of registers
These are not enough for most applications
Memory is just a vast collection of value holders
Values are copied to and from registers
Memory
CPU Memory
Core
reg
load
store
cell
Memory
Programmers see linear address space
Typical address space is wide 32 or 64 bits
Size of cell is limited (typically 1 byte, 8 bits)
Spread values in contiguous cells
We need to know starting address and size
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
32bit int
2
0
2
32 / 64
Little endian and big endian
How do we split (and reassembly) multibyte values
In computing endianness is the order of individually addressable sub-units within
a longer data word
Endianness may be seen as a low-level attribute of a particular representation
format, for example, the order in which the two bytes are stored in memory
Most modern computer processors agree on bit ordering "inside" individual
bytes. This means that any single-byte value will be read the same on almost
any computer one may send it to.
Integers are usually stored as sequences of bytes, so that the encoded value
can be obtained by simple concatenation. The two most common of them are:
0xABCDEF12
AB CD EF 12 AB CD EF 12
Big Endian Little Endian
Virtual memory
Each process thinks it has a large (2
32
- 2
64
) range of contiguous addresses
Certain logical addresses are mapped to physical addresses (RAM + disk file for
inactive parts)
The conversion mechanism is transparent
Overlapping addresses do not interfere
1007 1008 1009 1010 1011 1012 1013 1007 1008 1009 1010 1011 1012 1013
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
MMU + TLB
A B
Physical memory
Stack
Variables are allocated space on stack
Stack is a LIFO (Last-In-First-Out)
It has a fixed base and grows up or down
Newer variables allocated above earlier
Newer variables die before older
Push Pop
Older
Old
New New
Old
Older
int main(void){
int a = 20, b = 25;
...
Stack
-18
-17
-16
-15
-14
-13
-12
-11
-10
-9
-8
-7
20
0
0
0
25
0
0
0
a
b
SP
-6
-5
-4
int main(void){
int a = 20, b = 25;
{
int g;
...
Stack
-18
-17
-16
-15
-14
-13
-12
-11
-10
-9
-8
-7
20
0
0
0
25
0
0
0
a
b
SP
-6
-5
-4
garbage
garbage
garbage
garbage
g
int main(void){
int a = 20, b = 25;
{
int g;
g = func(72,73);
}
}
int func(int x, int y){
int a;
int b[3];
...
Stack
-32
-28
-24
-20
-16
-12
-8
-4
0
+4
+8
+12
20
25
garbage
73
72
ret
mfp
garbage
a
SP
+16
+20
+24
garbage
garbage
garbage
b
g
y
x
a
b[2]
b[1]
b[0]
FP
Copies of argument values
pushed into stack
Return address pushed into
stack
CPU IP aimed at beginning of
function block
All auto variables and
parameters are referenced via
offsets from the frame pointer
(FP)
The FP and Stack Pointer
(SP) are in registers
Stack
-32
-28
-24
-20
-16
-12
-8
-4
0
+4
+8
+12
20
25
garbage
73
72
ret
mfp
garbage
a
SP
+16
+20
+24
garbage
garbage
garbage
b
g
y
x
a
b[2]
b[1]
b[0]
FP
When func returns, the return
value is stored in a register
The stack pointer is moved to
the y location
The code is jumped to the
return address ret
Frame pointer is set to mfp
The caller moves the return
value to the right place
Stack
-60
-56
-52
-48
-44
-40
-36
-32
-28
-24
-20
-16
20
25
1
a
SP
-12
-8
-4
b
g
Heap
Heap memory, also known as dynamic memory is an alternative to local stack
memory
Why? Size of data may not be known in advance
May depend on user input
May depend on result of calculation
Size may change over time
We want to control lifetime of data stored over time
The heap is a large area of memory available for use by the program. The
program can request areas or blocks of memory for its use within the heap.
In order to allocate a block of some size the program makes an explicit request
by calling the heap allocation function
The allocation function reserves a block of memory of requested size in the heap
and returns a pointer to it
Heap
Each allocation request reserves a contiguous area of the requested size in the
heap and return a pointer to that new block to the program
The heap manager can allocate the blocks wherever it wants in the heap so long
as the blocks do not overlap
When the program is finished using a block of memory, it makes an explicit
deallocation request. The block is free again and so may be reused to satisfy
future allocations
Local Heap
Heap
The library functions which make heap requests are malloc() and free() (prototypes
in <stdlib.h>)
int *ar = NULL;
ar = (int *)malloc(30 * sizeof(int));
...
free(ar);
struct fraction *fracts;
fracts = malloc(10 * sizeof(struct fraction));
...
free(fracts);
A program which forgets to deallocate a block is said to have a memory leak
The result will be that the heap gradually fill up as there continue to be allocation
requests
Programmer is responsible for releasing dynamically allocated memory ASAP
Memory segmentation
In a computer system using segmentation, an instruction operand that refers to a
memory location includes a value that identifies a segment and an offset within
that segment. A segment has a set of permissions, and a length, associated with
it
When a program is executed it is read into memory where it resides until
termination. The code allocates a number of special purpose memory blocks for
different data types
STACK
a very dynamic kind of memory located at it's top (high addresses) and growing downwards
memory not allocated yet
Memory that will soon become allocated by the stack, that grows down. Stack will grow until it hits the administrative limit
(predefined).
shared libraries
memory not allocated yet
Memory that will soon become allocated by the heap growing up from underneath.
HEAP
It is said that this is the most dynamic part of memory. It is dynamically allocated and freed in big chunks. The allocation
process is rather complex (stub/buddy system) and is more time consuming than putting things on stack.
BSS
Memory containing global variables of known (predeclared) size.
Constant data
All constants used in a program.
Static program code
Reserved / Interrupts vector Tables / Memory mapped Devices / Other Stuff
Global Data Section
(Global and Static vars)
Run-Time Stack
(Local and Auto vars)
R1 (stack pointer)
PC
0000
FFFF
Heap
(Dynamically allocated vars)
I/O Space
Interrupt Vector Table
8000
Program Code
0600
Memory Layout
(typical in microcontrollers)
Memory segmentation
In the PC architecture there are four basic read-write memory regions in a
program:
DATA. The Data area contains global and static variables used by the
program that are initialized
BSS. In C, statically-allocated variables without an explicit initializer are
initialized to zero (for arithmetic types) or a null pointer (for pointer types).
Implementations of C typically represent zero values and null pointer values
using a bit pattern consisting solely of zero-valued bits (though this is not
required by the C standard). Hence, the bss section typically includes all
uninitialized variables declared at the file level (i.e., outside of any function)
as well as uninitialized local variables declared with the static keyword
HEAP. The Heap area is managed by malloc, realloc, and free. The Heap
area is shared by all shared libraries and dynamically loaded modules in a
process.
STACK. The stack is a LIFO structure, typically located in the higher parts of
memory. It usually "grows down" with every register, immediate value or stack
frame being added to it
Memory segmentation
And finally:
CODE. In computing, a code segment, also known as a text segment or
simply as text, is a phrase used to refer to a portion of memory or of an object
file that contains executable instructions. It has a fixed size and is usually
read-only. If read-only, it is reentrant.
Process (vs Thread)
In computing, a process is an instance of a computer program that is being
executed. It contains the program code and its current activity. Depending on the
operating system (OS), a process may be made up of multiple threads of
execution that execute instructions concurrently
In general, a computer system process consists of (or is said to 'own') the
following resources:
An image of the executable machine code associated with a program
Memory (typically some region of virtual memory); which includes the
executable code, process-specific data (input and output), a call stack (to
keep track of active subroutines and/or other events), and a heap to hold
intermediate computation data generated during run time
Operating system descriptors
Security attributes
Processor state (context), such as the content of registers, physical memory
addressing, etc
Process (vs Thread)
Under Unix and Unix-like operating systems, the parent and the child processes
can tell each other apart by examining the return value of the fork() system call
When a fork() system call is issued, a copy of all the pages corresponding to the
parent process is created, loaded into a separate memory location by the OS for
the child process
But this is not needed in certain cases. Consider the case when a child executes
an "exec" system call (which is used to execute any executable file from within a
C program) or exits very soon after the fork()
In some cases, a technique called copy-on-write (COW) is used. With this
technique, when a fork occurs, the parent process's pages are not copied for the
child process. Instead, the pages are shared between the child and the parent
process. Whenever a process (parent or child) modifies a page, a separate copy
of that particular page alone is made for that process (parent or child) which
performed the modification
(Process vs) Thread
Threads differ from traditional multitasking operating system processes in that:
processes are typically independent, while threads exist as subsets of a
process
processes carry considerable state information, whereas multiple threads
within a process share state as well as memory and other resources
processes have separate address spaces, whereas threads share their
address space
processes interact only through system-provided inter-process
communication mechanisms
Context switching between threads in the same process is typically faster
than context switching between processes
(Process vs) Thread
Elements per process Elements per thread
Address space Program counter
Global variables Registers
Open files Stack
Children processes State
Pending alarms
Signals and signal handlers
Account informations
Process
Address Space
Global
data
Code
Heap
Stack for
thread1
Stack for
thread2
Threads
Address Space
Global
data
Code
Heap
Threads
Address Space
Global
data
Code
Heap
Stack Stack
Compiler options
Option Description
-c Do NOT link.
Useful in a multiple file project to compile single files saprately (i.e. Counter.cpp).
Linking step is usually performed later.
-g Generate debug symbols. Without this, you cannot debug (KDBG, Eclipse etc).
Drawback: file is bigger and code is slower since it is not optimized
-I<dir> At compile time, include a directory to look for included files (i.e. headers).
By default, -I. (current dir) is added
-L<dir> At linking time, include a directory to look for object files/libraries: .o, .a.
By default, -L. (current dir) is added
-l<name> At linking time, links lib<name>.a to the executable
-o Output file name.
If you compile a single file, use SOURCENAME.o as the object file name. If you are
linking an entire executable (your full program), I strongly recommend PROGNAME.x,
.exe. Default (ugly) name is a.out
Remember: good programming starts from the file name! A well written file name lets
you to identify immediately the meaning of each file!!!
-O<n> Optimization level (<n> from 0 to 3)